Skip to content

Commit f8c9162

Browse files
tdekoningTed de Koning
and
Ted de Koning
authored
Adding support for specifying what browser needs to be used for authorization (FormidableLabs#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 <[email protected]>
1 parent 5fc39fe commit f8c9162

File tree

7 files changed

+253
-63
lines changed

7 files changed

+253
-63
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ with optional overrides.
136136
- **useNonce** - (`boolean`) (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers
137137
- **usePKCE** - (`boolean`) (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.
138138
- **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)
139+
- **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.
140+
- **androidAllowCustomBrowsers** - (`string[]`) (default: undefined) _ANDROID_ override the used browser for authorization. If no value is provided, all browsers are allowed.
139141
- **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.
140142

141143
#### result
@@ -351,7 +353,7 @@ Make `AppDelegate` conform to `RNAppAuthAuthorizationFlowManager` with the follo
351353
+ @property(nonatomic, weak)id<RNAppAuthAuthorizationFlowManagerDelegate>authorizationFlowManagerDelegate;
352354
```
353355
354-
Add the following code to `AppDelegate.m` (to support iOS <= 10 and React Navigation deep linking)
356+
Add the following code to `AppDelegate.m` (to support iOS <= 10, React Navigation deep linking and overriding browser behavior in the authorization process)
355357
356358
```diff
357359
+ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *, id> *) options {

android/src/main/java/com/rnappauth/RNAppAuthModule.java

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.facebook.react.bridge.ReadableType;
2626

2727
import com.rnappauth.utils.MapUtil;
28+
import com.rnappauth.utils.MutableBrowserAllowList;
2829
import com.rnappauth.utils.UnsafeConnectionBuilder;
2930
import com.rnappauth.utils.RegistrationResponseFactory;
3031
import com.rnappauth.utils.TokenResponseFactory;
@@ -46,6 +47,9 @@
4647
import net.openid.appauth.ResponseTypeValues;
4748
import net.openid.appauth.TokenResponse;
4849
import net.openid.appauth.TokenRequest;
50+
import net.openid.appauth.browser.AnyBrowserMatcher;
51+
import net.openid.appauth.browser.BrowserMatcher;
52+
import net.openid.appauth.browser.VersionedBrowserMatcher;
4953
import net.openid.appauth.EndSessionRequest;
5054
import net.openid.appauth.EndSessionResponse;
5155
import net.openid.appauth.connectivity.ConnectionBuilder;
@@ -164,7 +168,7 @@ public void register(
164168
) {
165169
this.parseHeaderMap(headers);
166170
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.registrationRequestHeaders, connectionTimeoutMillis);
167-
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests);
171+
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, null);
168172
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
169173

170174
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
@@ -233,11 +237,12 @@ public void authorize(
233237
final String clientAuthMethod,
234238
final boolean dangerouslyAllowInsecureHttpRequests,
235239
final ReadableMap headers,
240+
final ReadableArray androidAllowCustomBrowsers,
236241
final Promise promise
237242
) {
238243
this.parseHeaderMap(headers);
239244
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.authorizationRequestHeaders, connectionTimeoutMillis);
240-
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests);
245+
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers);
241246
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
242247

243248
// store args in private fields for later use in onActivityResult handler
@@ -325,11 +330,12 @@ public void refresh(
325330
final String clientAuthMethod,
326331
final boolean dangerouslyAllowInsecureHttpRequests,
327332
final ReadableMap headers,
333+
final ReadableArray androidAllowCustomBrowsers,
328334
final Promise promise
329335
) {
330336
this.parseHeaderMap(headers);
331337
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders, connectionTimeoutMillis);
332-
final AppAuthConfiguration appAuthConfiguration = createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests);
338+
final AppAuthConfiguration appAuthConfiguration = createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers);
333339
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
334340

335341
if (clientSecret != null) {
@@ -409,10 +415,11 @@ public void logout(
409415
final ReadableMap serviceConfiguration,
410416
final ReadableMap additionalParameters,
411417
final boolean dangerouslyAllowInsecureHttpRequests,
418+
final ReadableArray androidAllowCustomBrowsers,
412419
final Promise promise
413420
) {
414421
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, null);
415-
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests);
422+
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder, dangerouslyAllowInsecureHttpRequests, androidAllowCustomBrowsers);
416423
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
417424

418425
this.promise = promise;
@@ -507,7 +514,8 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
507514
final Promise authorizePromise = this.promise;
508515
final AppAuthConfiguration configuration = createAppAuthConfiguration(
509516
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders),
510-
this.dangerouslyAllowInsecureHttpRequests
517+
this.dangerouslyAllowInsecureHttpRequests,
518+
null
511519
);
512520

513521
AuthorizationService authService = new AuthorizationService(this.reactContext, configuration);
@@ -885,10 +893,12 @@ private List<Uri> arrayToUriList(ReadableArray array) {
885893
*/
886894
private AppAuthConfiguration createAppAuthConfiguration(
887895
ConnectionBuilder connectionBuilder,
888-
Boolean skipIssuerHttpsCheck
896+
Boolean skipIssuerHttpsCheck,
897+
ReadableArray androidAllowCustomBrowsers
889898
) {
890899
return new AppAuthConfiguration
891900
.Builder()
901+
.setBrowserMatcher(getBrowserAllowList(androidAllowCustomBrowsers))
892902
.setConnectionBuilder(connectionBuilder)
893903
.setSkipIssuerHttpsCheck(skipIssuerHttpsCheck)
894904
.build();
@@ -1019,6 +1029,50 @@ private void setServiceConfiguration(@Nullable String issuer, AuthorizationServi
10191029
}
10201030
}
10211031

1032+
private BrowserMatcher getBrowserAllowList(ReadableArray androidAllowCustomBrowsers) {
1033+
if(androidAllowCustomBrowsers == null || androidAllowCustomBrowsers.size() == 0) {
1034+
return AnyBrowserMatcher.INSTANCE;
1035+
}
1036+
1037+
MutableBrowserAllowList browserMatchers = new MutableBrowserAllowList();
1038+
1039+
for(int i = 0; i < androidAllowCustomBrowsers.size(); i++) {
1040+
String browser = androidAllowCustomBrowsers.getString(i);
1041+
1042+
if(browser == null) {
1043+
continue;
1044+
}
1045+
1046+
switch (browser) {
1047+
case "chrome": {
1048+
browserMatchers.add(VersionedBrowserMatcher.CHROME_BROWSER);
1049+
break;
1050+
}
1051+
case "chromeCustomTab": {
1052+
browserMatchers.add(VersionedBrowserMatcher.CHROME_CUSTOM_TAB);
1053+
break;
1054+
}
1055+
case "firefox": {
1056+
browserMatchers.add(VersionedBrowserMatcher.FIREFOX_BROWSER);
1057+
break;
1058+
}
1059+
case "firefoxCustomTab": {
1060+
browserMatchers.add(VersionedBrowserMatcher.FIREFOX_CUSTOM_TAB);
1061+
break;
1062+
}
1063+
case "samsung": {
1064+
browserMatchers.add(VersionedBrowserMatcher.SAMSUNG_BROWSER);
1065+
break;
1066+
}
1067+
case "samsungCustomTab": {
1068+
browserMatchers.add(VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB);
1069+
break;
1070+
}
1071+
}
1072+
}
1073+
return browserMatchers;
1074+
}
1075+
10221076
@Override
10231077
public void onNewIntent(Intent intent) {
10241078

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.rnappauth.utils;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import net.openid.appauth.browser.BrowserDescriptor;
6+
import net.openid.appauth.browser.BrowserMatcher;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
public class MutableBrowserAllowList implements BrowserMatcher {
12+
13+
private final List<BrowserMatcher> mBrowserMatchers = new ArrayList<>();
14+
15+
public void add(BrowserMatcher browserMatcher) {
16+
mBrowserMatchers.add(browserMatcher);
17+
}
18+
19+
public void remove(BrowserMatcher browserMatcher) {
20+
mBrowserMatchers.remove(browserMatcher);
21+
}
22+
23+
@Override
24+
public boolean matches(@NonNull BrowserDescriptor descriptor) {
25+
for (BrowserMatcher matcher : mBrowserMatchers) {
26+
if (matcher.matches(descriptor)) {
27+
return true;
28+
}
29+
}
30+
31+
return false;
32+
}
33+
}

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ export type AuthConfiguration = BaseAuthConfiguration & {
7979
usePKCE?: boolean;
8080
warmAndPrefetchChrome?: boolean;
8181
skipCodeExchange?: boolean;
82+
iosCustomBrowser?: 'safari' | 'chrome' | 'opera' | 'firefox';
83+
androidAllowCustomBrowsers?: ('chrome' | 'chromeCustomTab' | 'firefox' | 'firefoxCustomTab' | 'samsung' | 'samsungCustomTab')[]
8284
};
8385

8486
export type EndSessionConfiguration = BaseAuthConfiguration & {

index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ export const authorize = ({
207207
customHeaders,
208208
additionalHeaders,
209209
skipCodeExchange = false,
210+
iosCustomBrowser = null,
211+
androidAllowCustomBrowsers = null,
210212
connectionTimeoutSeconds,
211213
}) => {
212214
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
@@ -235,12 +237,14 @@ export const authorize = ({
235237
nativeMethodArguments.push(clientAuthMethod);
236238
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
237239
nativeMethodArguments.push(customHeaders);
240+
nativeMethodArguments.push(androidAllowCustomBrowsers);
238241
}
239242

240243
if (Platform.OS === 'ios') {
241244
nativeMethodArguments.push(additionalHeaders);
242245
nativeMethodArguments.push(useNonce);
243246
nativeMethodArguments.push(usePKCE);
247+
nativeMethodArguments.push(iosCustomBrowser);
244248
}
245249

246250
return RNAppAuth.authorize(...nativeMethodArguments);
@@ -259,6 +263,8 @@ export const refresh = (
259263
dangerouslyAllowInsecureHttpRequests = false,
260264
customHeaders,
261265
additionalHeaders,
266+
iosCustomBrowser = null,
267+
androidAllowCustomBrowsers = null,
262268
connectionTimeoutSeconds,
263269
},
264270
{ refreshToken }
@@ -288,10 +294,12 @@ export const refresh = (
288294
nativeMethodArguments.push(clientAuthMethod);
289295
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
290296
nativeMethodArguments.push(customHeaders);
297+
nativeMethodArguments.push(androidAllowCustomBrowsers);
291298
}
292299

293300
if (Platform.OS === 'ios') {
294301
nativeMethodArguments.push(additionalHeaders);
302+
nativeMethodArguments.push(iosCustomBrowser);
295303
}
296304

297305
return RNAppAuth.refresh(...nativeMethodArguments);
@@ -347,6 +355,8 @@ export const logout = (
347355
serviceConfiguration,
348356
additionalParameters,
349357
dangerouslyAllowInsecureHttpRequests = false,
358+
iosCustomBrowser = null,
359+
androidAllowCustomBrowsers = null,
350360
},
351361
{ idToken, postLogoutRedirectUrl }
352362
) => {
@@ -364,6 +374,11 @@ export const logout = (
364374

365375
if (Platform.OS === 'android') {
366376
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
377+
nativeMethodArguments.push(androidAllowCustomBrowsers);
378+
}
379+
380+
if (Platform.OS === 'ios') {
381+
nativeMethodArguments.push(iosCustomBrowser);
367382
}
368383

369384
return RNAppAuth.logout(...nativeMethodArguments);

0 commit comments

Comments
 (0)