Skip to content

Commit

Permalink
Adding support for specifying what browser needs to be used for autho…
Browse files Browse the repository at this point in the history
…rization (#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 <ted@q42.nl>
  • Loading branch information
tdekoning and Ted de Koning authored Oct 15, 2022
1 parent 5fc39fe commit f8c9162
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 63 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -351,7 +353,7 @@ Make `AppDelegate` conform to `RNAppAuthAuthorizationFlowManager` with the follo
+ @property(nonatomic, weak)id<RNAppAuthAuthorizationFlowManagerDelegate>authorizationFlowManagerDelegate;
```
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<NSString *, id> *) options {
Expand Down
66 changes: 60 additions & 6 deletions android/src/main/java/com/rnappauth/RNAppAuthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);

// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
Expand Down Expand Up @@ -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<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);

// store args in private fields for later use in onActivityResult handler
Expand Down Expand Up @@ -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<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);

if (clientSecret != null) {
Expand Down Expand Up @@ -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<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);

this.promise = promise;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -885,10 +893,12 @@ private List<Uri> 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();
Expand Down Expand Up @@ -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) {

Expand Down
Original file line number Diff line number Diff line change
@@ -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<BrowserMatcher> 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;
}
}
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {
Expand Down
15 changes: 15 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ export const authorize = ({
customHeaders,
additionalHeaders,
skipCodeExchange = false,
iosCustomBrowser = null,
androidAllowCustomBrowsers = null,
connectionTimeoutSeconds,
}) => {
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
Expand Down Expand Up @@ -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);
Expand All @@ -259,6 +263,8 @@ export const refresh = (
dangerouslyAllowInsecureHttpRequests = false,
customHeaders,
additionalHeaders,
iosCustomBrowser = null,
androidAllowCustomBrowsers = null,
connectionTimeoutSeconds,
},
{ refreshToken }
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -347,6 +355,8 @@ export const logout = (
serviceConfiguration,
additionalParameters,
dangerouslyAllowInsecureHttpRequests = false,
iosCustomBrowser = null,
androidAllowCustomBrowsers = null,
},
{ idToken, postLogoutRedirectUrl }
) => {
Expand All @@ -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);
Expand Down
Loading

0 comments on commit f8c9162

Please sign in to comment.