diff --git a/flutter_appauth/example/lib/main.dart b/flutter_appauth/example/lib/main.dart index a47b0893..c81b447b 100644 --- a/flutter_appauth/example/lib/main.dart +++ b/flutter_appauth/example/lib/main.dart @@ -16,8 +16,9 @@ class MyApp extends StatefulWidget { State createState() => _MyAppState(); } -class _MyAppState extends State { +class _MyAppState extends State with WidgetsBindingObserver { bool _isBusy = false; + bool _isAuthFlowInProgress = false; final FlutterAppAuth _appAuth = const FlutterAppAuth(); String? _codeVerifier; @@ -62,6 +63,30 @@ class _MyAppState extends State { endSessionEndpoint: 'https://demo.duendesoftware.com/connect/endsession', ); + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + if (_isAuthFlowInProgress) { + setState(() { + _isAuthFlowInProgress = false; + _clearBusyState(); + }); + } + } + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -126,6 +151,19 @@ class _MyAppState extends State { ExternalUserAgent.sfSafariViewController), ), ), + if (Platform.isIOS) + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + child: const Text( + 'Auto code exchange using system browser (iOS only)', + textAlign: TextAlign.center, + ), + onPressed: () => _signInWithAutoCodeExchange( + externalUserAgent: + ExternalUserAgent.customBrowser), + ), + ), ElevatedButton( onPressed: _refreshToken != null ? _refresh : null, child: const Text('Refresh token'), @@ -335,21 +373,30 @@ class _MyAppState extends State { {ExternalUserAgent externalUserAgent = ExternalUserAgent.asWebAuthenticationSession}) async { try { - _setBusyState(); + _setBusyState( + isAuthFlow: externalUserAgent == ExternalUserAgent.customBrowser + ); /* This shows that we can also explicitly specify the endpoints rather than getting from the details from the discovery document. */ - final AuthorizationTokenResponse result = - await _appAuth.authorizeAndExchangeCode( + final Future authRequest = + _appAuth.authorizeAndExchangeCode( AuthorizationTokenRequest(_clientId, _redirectUrl, serviceConfiguration: _serviceConfiguration, scopes: _scopes, externalUserAgent: externalUserAgent), ); - /* + // Apply timeout only when using an external browser user agent. + final AuthorizationTokenResponse result = + externalUserAgent == ExternalUserAgent.customBrowser + ? await authRequest.timeout(const Duration(minutes: 2)) + : await authRequest; + + + /* This code block demonstrates passing in values for the prompt parameter. In this case it prompts the user login even if they have already signed in. the list of supported values depends on the @@ -402,10 +449,13 @@ class _MyAppState extends State { }); } - void _setBusyState() { + void _setBusyState({bool? isAuthFlow}) { setState(() { _error = ''; _isBusy = true; + if (isAuthFlow != null) { + _isAuthFlowInProgress = isAuthFlow; + } }); } diff --git a/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/AppAuthIOSAuthorization.m b/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/AppAuthIOSAuthorization.m index 32e8c19b..e5ec9ad9 100644 --- a/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/AppAuthIOSAuthorization.m +++ b/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/AppAuthIOSAuthorization.m @@ -173,6 +173,9 @@ @implementation AppAuthIOSAuthorization return [[OIDExternalUserAgentIOSSafariViewController alloc] initWithPresentingViewController:rootViewController]; } + if ([externalUserAgent integerValue] == CustomBrowser) { + return [OIDExternalUserAgentIOSCustomBrowser CustomBrowserSafari]; + } return [[OIDExternalUserAgentIOS alloc] initWithPresentingViewController:rootViewController]; } diff --git a/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppAuth.h b/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppAuth.h index 5683ab88..5a39685b 100644 --- a/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppAuth.h +++ b/flutter_appauth/ios/flutter_appauth/Sources/flutter_appauth/FlutterAppAuth.h @@ -61,7 +61,8 @@ static NSString *const END_SESSION_ERROR_MESSAGE_FORMAT = typedef NS_ENUM(NSInteger, ExternalUserAgent) { ASWebAuthenticationSession, EphemeralASWebAuthenticationSession, - SafariViewController + SafariViewController, + CustomBrowser }; @interface AppAuthAuthorization : NSObject diff --git a/flutter_appauth_platform_interface/lib/src/external_user_agent.dart b/flutter_appauth_platform_interface/lib/src/external_user_agent.dart index 036d5551..877e4992 100644 --- a/flutter_appauth_platform_interface/lib/src/external_user_agent.dart +++ b/flutter_appauth_platform_interface/lib/src/external_user_agent.dart @@ -45,5 +45,12 @@ enum ExternalUserAgent { /// Note that as this does not follow the best practices on using the /// appropriate native APIs based on the OS version, developers should use /// this at their own discretion. - sfSafariViewController + sfSafariViewController, + + + /// Indicates a preference for using an external user-agent, + /// suitable for e.g. the secure browser of a MDM solution, + /// SSO flows and using the cookies/context of the system main browser. + /// This is only applicable to iOS (fallback is [asWebAuthenticationSession]). + customBrowser }