Skip to content

Commit

Permalink
[flutter_appauth][flutter_appauth_platform_interface] added support f…
Browse files Browse the repository at this point in the history
…or end session requests (MaikuB#216)

* progress on end session on iOS

* various fixes

* further updates to platform interface to handle end session requests

* bump AppAuth iOS dependency to 1.4.0

* update AuthorizationServiceConfiguration to have nullable props

* complete wiring up end session request on Android and iOS

* update plugin for prerelease

* add missing changelog entry on SDK dependencies being bumped

* update Dart SDK constraints

* update readmes and add mention to changelog

* fix null endSessionEndpoint crash on iOS

* bump platform interface

* fix merge issue

* bump plugin

* Skip https issuer check if allowInsecureConnections is true. (MaikuB#228)

* bump to 2.0.0-dev.3

* bump AppAuth Android dependency

* create external user agent per request, which also issue where cancelling an end session request once won't allow for subsequent sessions to work

* fix example app so that refreshing access token works again

* bump for release

* fix imports in example app

* fix spacing in changelog

* bump platform interface to 4.0.0 stable release

* bump plugin to 2.0.0 stable

Co-authored-by: Roman Fürst <[email protected]>
  • Loading branch information
MaikuB and rfuerst87 authored Nov 17, 2021
1 parent 4d9aeee commit f8bd32e
Show file tree
Hide file tree
Showing 23 changed files with 518 additions and 146 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# flutter_appauth
A Flutter plugin that provides a wrapper for native AppAuth SDKs (https://appauth.io) used authenticating and authorizing users. The repository consists of the following folders

- flutter_appauth: code for the plugin
- flutter_appauth_platform_interface: the code for common platform interface
- [flutter_appauth](https://github.com/MaikuB/flutter_appauth/tree/master/flutter_appauth): code for the plugin
- [flutter_appauth_platform_interface](https://github.com/MaikuB/flutter_appauth/tree/master/flutter_appauth_platform_interface): the code for common platform interface
8 changes: 8 additions & 0 deletions flutter_appauth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2.0.0

* **Breaking change** `AuthorizationServiceConfiguration` constructor has changed to take named parameters
* Added `endSession()` method, `EndSessionRequest` and `EndSessionResponse` classes to support end session requests
* [Android] skips https issuer check if `allowInsecureConnections` is true. Thanks to the PR from [Roman Fürst](https://github.com/rfuerst87)
* Bumped AppAuth Android and iOS SDK dependencies
* Added FAQs section to readme to describe a common iOS issue with Azure B2C and Azure AD

## 1.1.1

* [Android] Migrate maven repository from jcenter to mavenCentral.
Expand Down
25 changes: 22 additions & 3 deletions flutter_appauth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode
);
```

If you already know the authorization and token endpoints, which may be because discovery isn't supported, then these could be explicitly specified
In the event that discovery isn't supported or that you already know the endpoints for your server, they could be explicitly specified in the event that the dis

```dart
final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
serviceConfiguration: AuthorizationServiceConfiguration('<authorization_endpoint>', '<token_endpoint>'),
scopes: ['openid','profile', 'email', 'offline_access', 'api']
serviceConfiguration: AuthorizationServiceConfiguration(authorizationEndpoint: '<authorization_endpoint>', tokenEndpooint: '<token_endpoint>', endSessionEndpoint: '<end_session_endpoint>'),
scopes: [...]
),
);
```
Expand Down Expand Up @@ -90,6 +90,19 @@ final TokenResponse result = await appAuth.token(TokenRequest('<client_id>', '<r
scopes: ['openid','profile', 'email', 'offline_access', 'api']));
```

### End session

If your server has an [end session endpoint](https://openid.net/specs/openid-connect-rpinitiated-1_0.html), you can trigger an end session request that is typically used for logging out of the built-in browser with code similar to what's shown below

```dart
await appAuth.endSession(EndSessionRequest(
idTokenHint: '<idToken>',
postLogoutRedirectUrl: '<postLogoutRedirectUrl>',
serviceConfiguration: AuthorizationServiceConfiguration(authorizationEndpoint: '<authorization_endpoint>', tokenEndpooint: '<token_endpoint>', endSessionEndpoint: '<end_session_endpoint>'));
```

The above code passes an `AuthorizationServiceConfiguration` with all the endpoints defined but alternatives are to specify an `issuer` or `discoveryUrl` like you would with the other APIs in the plugin (e.g. `authorizeAndExchangeCode()`).

## Android setup

Go to the `build.gradle` file for your Android app to specify the custom scheme so that there should be a section in it that look similar to the following but replace `<your_custom_scheme>` with the desired value
Expand Down Expand Up @@ -145,3 +158,9 @@ Go to the `Info.plist` for your iOS app to specify the custom scheme so that the
</dict>
</array>
```

## FAQs

**When connecting to Azure B2C or Azure AD, the login request redirects properly on Android but not on iOS. What's going on?**

The AppAuth iOS SDK has some logic to validate the redirect URL to see if it should be responsible for processing the redirect. This appears to be failing under certain circumstances. Adding a trailing slash to the redirect URL specified in your code has been reported to fix the issue.

Large diffs are not rendered by default.

64 changes: 49 additions & 15 deletions flutter_appauth/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:http/http.dart' as http;

void main() => runApp(MyApp());

Expand All @@ -17,6 +17,8 @@ class _MyAppState extends State<MyApp> {
String? _authorizationCode;
String? _refreshToken;
String? _accessToken;
String? _idToken;

final TextEditingController _authorizationCodeTextController =
TextEditingController();
final TextEditingController _accessTokenTextController =
Expand All @@ -27,14 +29,15 @@ class _MyAppState extends State<MyApp> {
final TextEditingController _idTokenTextController = TextEditingController();
final TextEditingController _refreshTokenTextController =
TextEditingController();
String _userInfo = '';
String? _userInfo;

// For a list of client IDs, go to https://demo.identityserver.io
final String _clientId = 'interactive.public';
final String _redirectUrl = 'io.identityserver.demo:/oauthredirect';
final String _issuer = 'https://demo.identityserver.io';
final String _discoveryUrl =
'https://demo.identityserver.io/.well-known/openid-configuration';
final String _postLogoutRedirectUrl = 'io.identityserver.demo:/';
final List<String> _scopes = <String>[
'openid',
'profile',
Expand All @@ -45,13 +48,10 @@ class _MyAppState extends State<MyApp> {

final AuthorizationServiceConfiguration _serviceConfiguration =
const AuthorizationServiceConfiguration(
'https://demo.identityserver.io/connect/authorize',
'https://demo.identityserver.io/connect/token');

@override
void initState() {
super.initState();
}
authorizationEndpoint: 'https://demo.identityserver.io/connect/authorize',
tokenEndpoint: 'https://demo.identityserver.io/connect/token',
endSessionEndpoint: 'https://demo.identityserver.io/connect/endsession',
);

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -95,6 +95,14 @@ class _MyAppState extends State<MyApp> {
child: const Text('Refresh token'),
onPressed: _refreshToken != null ? _refresh : null,
),
ElevatedButton(
child: const Text('End session'),
onPressed: _idToken != null
? () async {
await _endSession();
}
: null,
),
const Text('authorization code'),
TextField(
controller: _authorizationCodeTextController,
Expand All @@ -116,22 +124,48 @@ class _MyAppState extends State<MyApp> {
controller: _refreshTokenTextController,
),
const Text('test api results'),
Text(_userInfo),
Text(_userInfo ?? ''),
],
),
),
),
);
}

Future<void> _endSession() async {
try {
_setBusyState();
await _appAuth.endSession(EndSessionRequest(
idTokenHint: _idToken,
postLogoutRedirectUrl: _postLogoutRedirectUrl,
serviceConfiguration: _serviceConfiguration));
_clearSessionInfo();
} catch (_) {}
_clearBusyState();
}

void _clearSessionInfo() {
setState(() {
_codeVerifier = null;
_authorizationCode = null;
_authorizationCodeTextController.clear();
_accessToken = null;
_accessTokenTextController.clear();
_idToken = null;
_idTokenTextController.clear();
_refreshToken = null;
_refreshTokenTextController.clear();
_accessTokenExpirationTextController.clear();
_userInfo = null;
});
}

Future<void> _refresh() async {
try {
_setBusyState();
final TokenResponse? result = await _appAuth.token(TokenRequest(
_clientId, _redirectUrl,
refreshToken: _refreshToken,
discoveryUrl: _discoveryUrl,
scopes: _scopes));
refreshToken: _refreshToken, issuer: _issuer, scopes: _scopes));
_processTokenResponse(result);
await _testApi(result);
} catch (_) {
Expand Down Expand Up @@ -230,7 +264,7 @@ class _MyAppState extends State<MyApp> {
void _processAuthTokenResponse(AuthorizationTokenResponse response) {
setState(() {
_accessToken = _accessTokenTextController.text = response.accessToken!;
_idTokenTextController.text = response.idToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
Expand All @@ -250,7 +284,7 @@ class _MyAppState extends State<MyApp> {
void _processTokenResponse(TokenResponse? response) {
setState(() {
_accessToken = _accessTokenTextController.text = response!.accessToken!;
_idTokenTextController.text = response.idToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
Expand Down
Loading

0 comments on commit f8bd32e

Please sign in to comment.