From f8c9162d5b8654dda95ca63a67f608c294908fec Mon Sep 17 00:00:00 2001 From: Ted de Koning <1109781+tdekoning@users.noreply.github.com> Date: Sat, 15 Oct 2022 17:58:29 +0200 Subject: [PATCH] Adding support for specifying what browser needs to be used for authorization (#655) * iOS - Adding custom browser parameter (work in progress). * iOS - Making the ios custom browser parameter optional. * Readme - Adding documentation for new parameter. * Android - Adding allow browsers list parameter for authorize method. * Readme - Adding documentation about android custom browsers parameter. * Fixing white space. * Tests - Adjusting unit tests to include custom browser arguments. * Custom browser - Fixing compile issues. Co-authored-by: Ted de Koning --- README.md | 4 +- .../java/com/rnappauth/RNAppAuthModule.java | 66 +++++++++- .../utils/MutableBrowserAllowList.java | 33 +++++ index.d.ts | 2 + index.js | 15 +++ index.spec.js | 77 ++++++++---- ios/RNAppAuth.m | 119 +++++++++++++----- 7 files changed, 253 insertions(+), 63 deletions(-) create mode 100644 android/src/main/java/com/rnappauth/utils/MutableBrowserAllowList.java diff --git a/README.md b/README.md index 5961ae1b..6fb9867b 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ with optional overrides. - **useNonce** - (`boolean`) (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers - **usePKCE** - (`boolean`) (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers. - **skipCodeExchange** - (`boolean`) (default: false) just return the authorization response, instead of automatically exchanging the authorization code. This is useful if this exchange needs to be done manually (not client-side) +- **iosCustomBrowser** - (`string`) (default: undefined) _IOS_ override the used browser for authorization, used to open an external browser. If no value is provided, the `SFAuthenticationSession` or `SFSafariViewController` are used. +- **androidAllowCustomBrowsers** - (`string[]`) (default: undefined) _ANDROID_ override the used browser for authorization. If no value is provided, all browsers are allowed. - **connectionTimeoutSeconds** - (`number`) configure the request timeout interval in seconds. This must be a positive number. The default values are 60 seconds on iOS and 15 seconds on Android. #### result @@ -351,7 +353,7 @@ Make `AppDelegate` conform to `RNAppAuthAuthorizationFlowManager` with the follo + @property(nonatomic, weak)idauthorizationFlowManagerDelegate; ``` -Add the following code to `AppDelegate.m` (to support iOS <= 10 and React Navigation deep linking) +Add the following code to `AppDelegate.m` (to support iOS <= 10, React Navigation deep linking and overriding browser behavior in the authorization process) ```diff + - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *) options { diff --git a/android/src/main/java/com/rnappauth/RNAppAuthModule.java b/android/src/main/java/com/rnappauth/RNAppAuthModule.java index be5486f0..b775886e 100644 --- a/android/src/main/java/com/rnappauth/RNAppAuthModule.java +++ b/android/src/main/java/com/rnappauth/RNAppAuthModule.java @@ -25,6 +25,7 @@ import com.facebook.react.bridge.ReadableType; import com.rnappauth.utils.MapUtil; +import com.rnappauth.utils.MutableBrowserAllowList; import com.rnappauth.utils.UnsafeConnectionBuilder; import com.rnappauth.utils.RegistrationResponseFactory; import com.rnappauth.utils.TokenResponseFactory; @@ -46,6 +47,9 @@ import net.openid.appauth.ResponseTypeValues; import net.openid.appauth.TokenResponse; import net.openid.appauth.TokenRequest; +import net.openid.appauth.browser.AnyBrowserMatcher; +import net.openid.appauth.browser.BrowserMatcher; +import net.openid.appauth.browser.VersionedBrowserMatcher; import net.openid.appauth.EndSessionRequest; import net.openid.appauth.EndSessionResponse; import net.openid.appauth.connectivity.ConnectionBuilder; @@ -164,7 +168,7 @@ public void register( ) { this.parseHeaderMap(headers); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.registrationRequestHeaders, connectionTimeoutMillis); - final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, null); final HashMap additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters); // when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint @@ -233,11 +237,12 @@ public void authorize( final String clientAuthMethod, final boolean dangerouslyAllowInsecureHttpRequests, final ReadableMap headers, + final ReadableArray androidAllowCustomBrowsers, final Promise promise ) { this.parseHeaderMap(headers); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.authorizationRequestHeaders, connectionTimeoutMillis); - final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers); final HashMap additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters); // store args in private fields for later use in onActivityResult handler @@ -325,11 +330,12 @@ public void refresh( final String clientAuthMethod, final boolean dangerouslyAllowInsecureHttpRequests, final ReadableMap headers, + final ReadableArray androidAllowCustomBrowsers, final Promise promise ) { this.parseHeaderMap(headers); final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders, connectionTimeoutMillis); - final AppAuthConfiguration appAuthConfiguration = createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration appAuthConfiguration = createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers); final HashMap additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters); if (clientSecret != null) { @@ -409,10 +415,11 @@ public void logout( final ReadableMap serviceConfiguration, final ReadableMap additionalParameters, final boolean dangerouslyAllowInsecureHttpRequests, + final ReadableArray androidAllowCustomBrowsers, final Promise promise ) { final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, null); - final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests); + final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers); final HashMap additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters); this.promise = promise; @@ -507,7 +514,8 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, final Promise authorizePromise = this.promise; final AppAuthConfiguration configuration = createAppAuthConfiguration( createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders), - this.dangerouslyAllowInsecureHttpRequests + this.dangerouslyAllowInsecureHttpRequests, + null ); AuthorizationService authService = new AuthorizationService(this.reactContext, configuration); @@ -885,10 +893,12 @@ private List arrayToUriList(ReadableArray array) { */ private AppAuthConfiguration createAppAuthConfiguration( ConnectionBuilder connectionBuilder, - Boolean skipIssuerHttpsCheck + Boolean skipIssuerHttpsCheck, + ReadableArray androidAllowCustomBrowsers ) { return new AppAuthConfiguration .Builder() + .setBrowserMatcher(getBrowserAllowList(androidAllowCustomBrowsers)) .setConnectionBuilder(connectionBuilder) .setSkipIssuerHttpsCheck(skipIssuerHttpsCheck) .build(); @@ -1019,6 +1029,50 @@ private void setServiceConfiguration(@Nullable String issuer, AuthorizationServi } } + private BrowserMatcher getBrowserAllowList(ReadableArray androidAllowCustomBrowsers) { + if(androidAllowCustomBrowsers == null || androidAllowCustomBrowsers.size() == 0) { + return AnyBrowserMatcher.INSTANCE; + } + + MutableBrowserAllowList browserMatchers = new MutableBrowserAllowList(); + + for(int i = 0; i < androidAllowCustomBrowsers.size(); i++) { + String browser = androidAllowCustomBrowsers.getString(i); + + if(browser == null) { + continue; + } + + switch (browser) { + case "chrome": { + browserMatchers.add(VersionedBrowserMatcher.CHROME_BROWSER); + break; + } + case "chromeCustomTab": { + browserMatchers.add(VersionedBrowserMatcher.CHROME_CUSTOM_TAB); + break; + } + case "firefox": { + browserMatchers.add(VersionedBrowserMatcher.FIREFOX_BROWSER); + break; + } + case "firefoxCustomTab": { + browserMatchers.add(VersionedBrowserMatcher.FIREFOX_CUSTOM_TAB); + break; + } + case "samsung": { + browserMatchers.add(VersionedBrowserMatcher.SAMSUNG_BROWSER); + break; + } + case "samsungCustomTab": { + browserMatchers.add(VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB); + break; + } + } + } + return browserMatchers; + } + @Override public void onNewIntent(Intent intent) { diff --git a/android/src/main/java/com/rnappauth/utils/MutableBrowserAllowList.java b/android/src/main/java/com/rnappauth/utils/MutableBrowserAllowList.java new file mode 100644 index 00000000..91204ecf --- /dev/null +++ b/android/src/main/java/com/rnappauth/utils/MutableBrowserAllowList.java @@ -0,0 +1,33 @@ +package com.rnappauth.utils; + +import androidx.annotation.NonNull; + +import net.openid.appauth.browser.BrowserDescriptor; +import net.openid.appauth.browser.BrowserMatcher; + +import java.util.ArrayList; +import java.util.List; + +public class MutableBrowserAllowList implements BrowserMatcher { + + private final List mBrowserMatchers = new ArrayList<>(); + + public void add(BrowserMatcher browserMatcher) { + mBrowserMatchers.add(browserMatcher); + } + + public void remove(BrowserMatcher browserMatcher) { + mBrowserMatchers.remove(browserMatcher); + } + + @Override + public boolean matches(@NonNull BrowserDescriptor descriptor) { + for (BrowserMatcher matcher : mBrowserMatchers) { + if (matcher.matches(descriptor)) { + return true; + } + } + + return false; + } +} diff --git a/index.d.ts b/index.d.ts index f7d4ad94..a380b8a8 100644 --- a/index.d.ts +++ b/index.d.ts @@ -79,6 +79,8 @@ export type AuthConfiguration = BaseAuthConfiguration & { usePKCE?: boolean; warmAndPrefetchChrome?: boolean; skipCodeExchange?: boolean; + iosCustomBrowser?: 'safari' | 'chrome' | 'opera' | 'firefox'; + androidAllowCustomBrowsers?: ('chrome' | 'chromeCustomTab' | 'firefox' | 'firefoxCustomTab' | 'samsung' | 'samsungCustomTab')[] }; export type EndSessionConfiguration = BaseAuthConfiguration & { diff --git a/index.js b/index.js index b9691060..019cdd23 100644 --- a/index.js +++ b/index.js @@ -207,6 +207,8 @@ export const authorize = ({ customHeaders, additionalHeaders, skipCodeExchange = false, + iosCustomBrowser = null, + androidAllowCustomBrowsers = null, connectionTimeoutSeconds, }) => { validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration); @@ -235,12 +237,14 @@ export const authorize = ({ nativeMethodArguments.push(clientAuthMethod); nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); nativeMethodArguments.push(customHeaders); + nativeMethodArguments.push(androidAllowCustomBrowsers); } if (Platform.OS === 'ios') { nativeMethodArguments.push(additionalHeaders); nativeMethodArguments.push(useNonce); nativeMethodArguments.push(usePKCE); + nativeMethodArguments.push(iosCustomBrowser); } return RNAppAuth.authorize(...nativeMethodArguments); @@ -259,6 +263,8 @@ export const refresh = ( dangerouslyAllowInsecureHttpRequests = false, customHeaders, additionalHeaders, + iosCustomBrowser = null, + androidAllowCustomBrowsers = null, connectionTimeoutSeconds, }, { refreshToken } @@ -288,10 +294,12 @@ export const refresh = ( nativeMethodArguments.push(clientAuthMethod); nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); nativeMethodArguments.push(customHeaders); + nativeMethodArguments.push(androidAllowCustomBrowsers); } if (Platform.OS === 'ios') { nativeMethodArguments.push(additionalHeaders); + nativeMethodArguments.push(iosCustomBrowser); } return RNAppAuth.refresh(...nativeMethodArguments); @@ -347,6 +355,8 @@ export const logout = ( serviceConfiguration, additionalParameters, dangerouslyAllowInsecureHttpRequests = false, + iosCustomBrowser = null, + androidAllowCustomBrowsers = null, }, { idToken, postLogoutRedirectUrl } ) => { @@ -364,6 +374,11 @@ export const logout = ( if (Platform.OS === 'android') { nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests); + nativeMethodArguments.push(androidAllowCustomBrowsers); + } + + if (Platform.OS === 'ios') { + nativeMethodArguments.push(iosCustomBrowser); } return RNAppAuth.logout(...nativeMethodArguments); diff --git a/index.spec.js b/index.spec.js index 7bb43021..29b53fd4 100644 --- a/index.spec.js +++ b/index.spec.js @@ -60,6 +60,8 @@ describe('AppAuth', () => { additionalHeaders: { header: 'value' }, connectionTimeoutSeconds: TIMEOUT_SEC, skipCodeExchange: false, + iosCustomBrowser: 'safari', + androidAllowCustomBrowsers: ['chrome'], }; const registerConfig = { @@ -530,7 +532,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, config.useNonce, - config.usePKCE + config.usePKCE, + config.iosCustomBrowser ); }); @@ -558,7 +561,8 @@ describe('AppAuth', () => { DEFAULT_TIMEOUT_IOS, null, true, - true + true, + null ); }); @@ -582,7 +586,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, config.useNonce, - config.usePKCE + config.usePKCE, + config.iosCustomBrowser ); }); }); @@ -603,7 +608,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, additionalHeaders, config.useNonce, - config.usePKCE + config.usePKCE, + config.iosCustomBrowser ); }); @@ -634,7 +640,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, true, - true + true, + config.iosCustomBrowser ); }); @@ -652,7 +659,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, false, - true + true, + config.iosCustomBrowser ); }); }); @@ -672,7 +680,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, config.useNonce, - true + true, + config.iosCustomBrowser ); }); @@ -690,7 +699,8 @@ describe('AppAuth', () => { config.connectionTimeoutSeconds, config.additionalHeaders, config.useNonce, - false + false, + config.iosCustomBrowser ); }); }); @@ -718,7 +728,8 @@ describe('AppAuth', () => { config.usePKCE, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -740,7 +751,8 @@ describe('AppAuth', () => { config.usePKCE, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); @@ -760,7 +772,8 @@ describe('AppAuth', () => { config.usePKCE, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); @@ -780,7 +793,8 @@ describe('AppAuth', () => { config.usePKCE, config.clientAuthMethod, true, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -809,7 +823,8 @@ describe('AppAuth', () => { config.usePKCE, config.clientAuthMethod, false, - customHeaders + customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -887,7 +902,8 @@ describe('AppAuth', () => { config.additionalParameters, config.serviceConfiguration, config.connectionTimeoutSeconds, - config.additionalHeaders + config.additionalHeaders, + config.iosCustomBrowser ); }); @@ -909,7 +925,8 @@ describe('AppAuth', () => { config.additionalParameters, config.serviceConfiguration, config.connectionTimeoutSeconds, - config.additionalHeaders + config.additionalHeaders, + config.iosCustomBrowser ); }); }); @@ -928,7 +945,8 @@ describe('AppAuth', () => { config.additionalParameters, config.serviceConfiguration, config.connectionTimeoutSeconds, - additionalHeaders + additionalHeaders, + config.iosCustomBrowser ); }); @@ -960,7 +978,8 @@ describe('AppAuth', () => { TIMEOUT_MILLIS, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -980,7 +999,8 @@ describe('AppAuth', () => { TIMEOUT_MILLIS, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); @@ -998,7 +1018,8 @@ describe('AppAuth', () => { TIMEOUT_MILLIS, config.clientAuthMethod, false, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); @@ -1016,7 +1037,8 @@ describe('AppAuth', () => { TIMEOUT_MILLIS, config.clientAuthMethod, true, - config.customHeaders + config.customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -1039,7 +1061,8 @@ describe('AppAuth', () => { TIMEOUT_MILLIS, config.clientAuthMethod, false, - customHeaders + customHeaders, + config.androidAllowCustomBrowsers ); }); }); @@ -1096,7 +1119,8 @@ describe('AppAuth', () => { '_token_', '_redirect_', config.serviceConfiguration, - config.additionalParameters + config.additionalParameters, + config.iosCustomBrowser ); }); }); @@ -1114,7 +1138,8 @@ describe('AppAuth', () => { '_redirect_', config.serviceConfiguration, config.additionalParameters, - false + false, + config.androidAllowCustomBrowsers ); }); @@ -1129,7 +1154,8 @@ describe('AppAuth', () => { '_redirect_', config.serviceConfiguration, config.additionalParameters, - true + true, + config.androidAllowCustomBrowsers ); }); @@ -1144,7 +1170,8 @@ describe('AppAuth', () => { '_redirect_', config.serviceConfiguration, config.additionalParameters, - false + false, + config.androidAllowCustomBrowsers ); }); }); diff --git a/ios/RNAppAuth.m b/ios/RNAppAuth.m index 500962e4..7f5b7f9c 100644 --- a/ios/RNAppAuth.m +++ b/ios/RNAppAuth.m @@ -97,6 +97,7 @@ - (dispatch_queue_t)methodQueue additionalHeaders: (NSDictionary *_Nullable) additionalHeaders useNonce: (BOOL *) useNonce usePKCE: (BOOL *) usePKCE + iosCustomBrowser: (NSString *) iosCustomBrowser resolve: (RCTPromiseResolveBlock) resolve reject: (RCTPromiseRejectBlock) reject) { @@ -114,6 +115,7 @@ - (dispatch_queue_t)methodQueue usePKCE: usePKCE additionalParameters: additionalParameters skipCodeExchange: skipCodeExchange + iosCustomBrowser: iosCustomBrowser resolve: resolve reject: reject]; } else { @@ -124,6 +126,7 @@ - (dispatch_queue_t)methodQueue return; } [self authorizeWithConfiguration: configuration + redirectUrl: redirectUrl clientId: clientId clientSecret: clientSecret @@ -132,6 +135,7 @@ - (dispatch_queue_t)methodQueue usePKCE: usePKCE additionalParameters: additionalParameters skipCodeExchange: skipCodeExchange + iosCustomBrowser: iosCustomBrowser resolve: resolve reject: reject]; }]; @@ -149,6 +153,7 @@ - (dispatch_queue_t)methodQueue serviceConfiguration: (NSDictionary *_Nullable) serviceConfiguration connectionTimeoutSeconds: (double) connectionTimeoutSeconds additionalHeaders: (NSDictionary *_Nullable) additionalHeaders + iosCustomBrowser: (NSString *) iosCustomBrowser resolve:(RCTPromiseResolveBlock) resolve reject: (RCTPromiseRejectBlock) reject) { @@ -193,6 +198,7 @@ - (dispatch_queue_t)methodQueue postLogoutRedirectURL: (NSString *) postLogoutRedirectURL serviceConfiguration: (NSDictionary *_Nullable) serviceConfiguration additionalParameters: (NSDictionary *_Nullable) additionalParameters + iosCustomBrowser: (NSString *) iosCustomBrowser resolve:(RCTPromiseResolveBlock) resolve reject: (RCTPromiseRejectBlock) reject) { @@ -202,6 +208,7 @@ - (dispatch_queue_t)methodQueue idTokenHint: idTokenHint postLogoutRedirectURL: postLogoutRedirectURL additionalParameters: additionalParameters + iosCustomBrowser: iosCustomBrowser resolve: resolve reject: reject]; @@ -216,6 +223,7 @@ - (dispatch_queue_t)methodQueue idTokenHint: idTokenHint postLogoutRedirectURL: postLogoutRedirectURL additionalParameters: additionalParameters + iosCustomBrowser: iosCustomBrowser resolve: resolve reject: reject]; }]; @@ -313,6 +321,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration usePKCE: (BOOL *) usePKCE additionalParameters: (NSDictionary *_Nullable) additionalParameters skipCodeExchange: (BOOL) skipCodeExchange + iosCustomBrowser: (NSString *) iosCustomBrowser resolve: (RCTPromiseResolveBlock) resolve reject: (RCTPromiseRejectBlock) reject { @@ -325,6 +334,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration OIDAuthorizationRequest *request = [[OIDAuthorizationRequest alloc] initWithConfiguration:configuration clientId:clientId + clientSecret:clientSecret scope:[OIDScopeUtilities scopesWithArray:scopes] redirectURL:[NSURL URLWithString:redirectUrl] @@ -351,39 +361,58 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration }]; UIViewController *presentingViewController = appDelegate.window.rootViewController.view.window ? appDelegate.window.rootViewController : appDelegate.window.rootViewController.presentedViewController; + id externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : nil; + + OIDAuthorizationCallback callback = ^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { + typeof(self) strongSelf = weakSelf; + strongSelf->_currentSession = nil; + [UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId]; + rnAppAuthTaskId = UIBackgroundTaskInvalid; + if (authorizationResponse) { + resolve([self formatAuthorizationResponse:authorizationResponse withCodeVerifier:codeVerifier]); + } else { + reject([self getErrorCode: error defaultCode:@"authentication_failed"], + [self getErrorMessage: error], error); + } + }; if (skipCodeExchange) { - _currentSession = [OIDAuthorizationService presentAuthorizationRequest:request - presentingViewController:presentingViewController - callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error) { - typeof(self) strongSelf = weakSelf; - strongSelf->_currentSession = nil; - [UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId]; - rnAppAuthTaskId = UIBackgroundTaskInvalid; - if (authorizationResponse) { - resolve([self formatAuthorizationResponse:authorizationResponse withCodeVerifier:codeVerifier]); - } else { - reject([self getErrorCode: error defaultCode:@"authentication_failed"], - [self getErrorMessage: error], error); - } - }]; // end [OIDAuthState presentAuthorizationRequest:request + + if(externalUserAgent != nil) { + _currentSession = [OIDAuthorizationService presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:callback]; + } else { + _currentSession = [OIDAuthorizationService presentAuthorizationRequest:request + presentingViewController:presentingViewController + callback:callback]; + } } else { - _currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request - presentingViewController:presentingViewController - callback:^(OIDAuthState *_Nullable authState, - NSError *_Nullable error) { - typeof(self) strongSelf = weakSelf; - strongSelf->_currentSession = nil; - [UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId]; - rnAppAuthTaskId = UIBackgroundTaskInvalid; - if (authState) { - resolve([self formatResponse:authState.lastTokenResponse - withAuthResponse:authState.lastAuthorizationResponse]); - } else { - reject([self getErrorCode: error defaultCode:@"authentication_failed"], - [self getErrorMessage: error], error); - } - }]; // end [OIDAuthState authStateByPresentingAuthorizationRequest:request + + if(externalUserAgent != nil) { + _currentSession = [OIDAuthorizationService presentAuthorizationRequest:request + externalUserAgent:externalUserAgent + callback:callback]; + } else { + OIDAuthStateAuthorizationCallback callback = ^(OIDAuthState *_Nullable authState, + NSError *_Nullable error) { + typeof(self) strongSelf = weakSelf; + strongSelf->_currentSession = nil; + [UIApplication.sharedApplication endBackgroundTask:rnAppAuthTaskId]; + rnAppAuthTaskId = UIBackgroundTaskInvalid; + if (authState) { + resolve([self formatResponse:authState.lastTokenResponse + withAuthResponse:authState.lastAuthorizationResponse]); + } else { + reject([self getErrorCode: error defaultCode:@"authentication_failed"], + [self getErrorMessage: error], error); + } + }; + + _currentSession = [OIDAuthState authStateByPresentingAuthorizationRequest:request + presentingViewController:presentingViewController + callback:callback]; + } } } @@ -428,6 +457,7 @@ - (void)endSessionWithConfiguration: (OIDServiceConfiguration *) configuration idTokenHint: (NSString *) idTokenHint postLogoutRedirectURL: (NSString *) postLogoutRedirectURL additionalParameters: (NSDictionary *_Nullable) additionalParameters + iosCustomBrowser: (NSString *) iosCustomBrowser resolve: (RCTPromiseResolveBlock) resolve reject: (RCTPromiseRejectBlock) reject { @@ -451,9 +481,11 @@ - (void)endSessionWithConfiguration: (OIDServiceConfiguration *) configuration }]; UIViewController *presentingViewController = appDelegate.window.rootViewController.view.window ? appDelegate.window.rootViewController : appDelegate.window.rootViewController.presentedViewController; + + id externalUserAgent = iosCustomBrowser != nil ? [self getCustomBrowser: iosCustomBrowser] : [self getExternalUserAgentWithPresentingViewController:presentingViewController]; _currentSession = [OIDAuthorizationService presentEndSessionRequest: endSessionRequest - externalUserAgent: [self getExternalUserAgentWithPresentingViewController:presentingViewController] + externalUserAgent: externalUserAgent callback: ^(OIDEndSessionResponse *_Nullable response, NSError *_Nullable error) { typeof(self) strongSelf = weakSelf; strongSelf->_currentSession = nil; @@ -625,6 +657,31 @@ - (NSString*)getErrorCode: (NSError*) error defaultCode: (NSString *) defaultCod return defaultCode; } +- (id)getCustomBrowser: (NSString *) browserType { + typedef id (^BrowserBlock)(void); + + NSDictionary *browsers = @{ + @"safari": + ^{ + return [OIDExternalUserAgentIOSCustomBrowser CustomBrowserSafari]; + }, + @"chrome": + ^{ + return [OIDExternalUserAgentIOSCustomBrowser CustomBrowserChrome]; + }, + @"opera": + ^{ + return [OIDExternalUserAgentIOSCustomBrowser CustomBrowserOpera]; + }, + @"firefox": + ^{ + return [OIDExternalUserAgentIOSCustomBrowser CustomBrowserFirefox]; + } + }; + BrowserBlock browser = browsers[browserType]; + return browser(); +} + - (NSString*)getErrorMessage: (NSError*) error { NSDictionary * userInfo = [error userInfo];