Skip to content

Commit 44a47c5

Browse files
michaelrhughesKadi Kraman
authored andcommitted
Adds a method to prefetch AuthorizationServiceConfiguration (#289)
* Add method to warm up the chrome custom tab on android * Fix linting * Fix linting #2 * Refactor/Rename to prefetchOnce which prefetches the AuthorizationServiceConfig and reusing it * Delete unnecessary chrome custom tab warmUp code, there is no difference * Fix: Forgot to call countDown on fetchConfLatch when serviceConfiguration was provided * Fix: Call CountDown method of latch when not entering either condition clause * Fix typo... * Fix: Call CountDown method of latch when not entering either condition clause, got lost somewhere * Remove unnecessary compiler options in build.gradle and make linting happy * Renamed prefetchOnce to prefetchConfiguration and updated README * Added in proper handling of headers * Fixed lint errors * Added in warming and prefetching of chrome custom tabs. * Addressed code review comments * Moved prefetch chrome option to be an argumnt in the config variable * More fixes to lint styling
1 parent 160e9af commit 44a47c5

File tree

4 files changed

+151
-5
lines changed

4 files changed

+151
-5
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ const config = {
8181
const result = await authorize(config);
8282
```
8383

84+
### `prefetchConfiguration`
85+
86+
ANDROID This will prefetch the authorization service configuration. Invoking this function is optional
87+
and will speed up calls to authorize. This is only supported on Android.
88+
89+
```js
90+
import { prefetchConfiguration } from 'react-native-app-auth';
91+
92+
const config = {
93+
warmAndPrefetchChrome: true,
94+
issuer: '<YOUR_ISSUER_URL>',
95+
clientId: '<YOUR_CLIENT_ID>',
96+
redirectUrl: '<YOUR_REDIRECT_URL>',
97+
scopes: ['<YOUR_SCOPES_ARRAY>'],
98+
};
99+
100+
prefetchConfiguration(config);
101+
```
102+
84103
#### config
85104

86105
This is your configuration object for the client. The config is passed into each of the methods

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

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
import android.app.Activity;
44
import android.app.PendingIntent;
5+
import android.content.ComponentName;
56
import android.content.Context;
67
import android.content.Intent;
78
import android.net.Uri;
9+
import android.os.Bundle;
810
import android.support.annotation.Nullable;
11+
import android.support.customtabs.CustomTabsCallback;
12+
import android.support.customtabs.CustomTabsClient;
13+
import android.support.customtabs.CustomTabsServiceConnection;
14+
import android.util.Log;
915

1016
import com.facebook.react.bridge.ActivityEventListener;
1117
import com.facebook.react.bridge.ReactApplicationContext;
@@ -37,11 +43,16 @@
3743
import net.openid.appauth.connectivity.ConnectionBuilder;
3844
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
3945

46+
import java.util.Collections;
4047
import java.util.HashMap;
4148
import java.util.Map;
49+
import java.util.concurrent.atomic.AtomicReference;
50+
import java.util.concurrent.CountDownLatch;
4251

4352
public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener {
4453

54+
public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome";
55+
4556
private final ReactApplicationContext reactContext;
4657
private Promise promise;
4758
private Boolean dangerouslyAllowInsecureHttpRequests;
@@ -50,13 +61,76 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
5061
private Map<String, String> tokenRequestHeaders = null;
5162
private Map<String, String> additionalParametersMap;
5263
private String clientSecret;
64+
private final AtomicReference<AuthorizationServiceConfiguration> mServiceConfiguration = new AtomicReference<>();
65+
private boolean isPrefetched = false;
5366

5467
public RNAppAuthModule(ReactApplicationContext reactContext) {
5568
super(reactContext);
5669
this.reactContext = reactContext;
5770
reactContext.addActivityEventListener(this);
5871
}
5972

73+
@ReactMethod
74+
public void prefetchConfiguration(
75+
final Boolean warmAndPrefetchChrome,
76+
final String issuer,
77+
final String redirectUrl,
78+
final String clientId,
79+
final ReadableArray scopes,
80+
final ReadableMap serviceConfiguration,
81+
final Boolean dangerouslyAllowInsecureHttpRequests,
82+
final ReadableMap headers,
83+
final Promise promise
84+
) {
85+
if (warmAndPrefetchChrome) {
86+
warmChromeCustomTab(reactContext, issuer);
87+
}
88+
89+
this.parseHeaderMap(headers);
90+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.authorizationRequestHeaders);
91+
final CountDownLatch fetchConfigurationLatch = new CountDownLatch(1);
92+
93+
if(!isPrefetched) {
94+
if (serviceConfiguration != null && mServiceConfiguration.get() == null) {
95+
try {
96+
mServiceConfiguration.set(createAuthorizationServiceConfiguration(serviceConfiguration));
97+
isPrefetched = true;
98+
fetchConfigurationLatch.countDown();
99+
} catch (Exception e) {
100+
promise.reject("RNAppAuth Error", "Failed to convert serviceConfiguration", e);
101+
}
102+
} else if (mServiceConfiguration.get() == null) {
103+
final Uri issuerUri = Uri.parse(issuer);
104+
AuthorizationServiceConfiguration.fetchFromUrl(
105+
buildConfigurationUriFromIssuer(issuerUri),
106+
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
107+
public void onFetchConfigurationCompleted(
108+
@Nullable AuthorizationServiceConfiguration fetchedConfiguration,
109+
@Nullable AuthorizationException ex) {
110+
if (ex != null) {
111+
promise.reject("RNAppAuth Error", "Failed to fetch configuration", ex);
112+
return;
113+
}
114+
mServiceConfiguration.set(fetchedConfiguration);
115+
isPrefetched = true;
116+
fetchConfigurationLatch.countDown();
117+
}
118+
},
119+
builder
120+
);
121+
}
122+
} else {
123+
fetchConfigurationLatch.countDown();
124+
}
125+
126+
try {
127+
fetchConfigurationLatch.await();
128+
promise.resolve(isPrefetched);
129+
} catch (Exception e) {
130+
promise.reject("RNAppAuth Error", "Failed to await fetch configuration", e);
131+
}
132+
}
133+
60134
@ReactMethod
61135
public void authorize(
62136
String issuer,
@@ -89,10 +163,11 @@ public void authorize(
89163
this.clientAuthMethod = clientAuthMethod;
90164

91165
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
92-
if (serviceConfiguration != null) {
166+
if (serviceConfiguration != null || mServiceConfiguration.get() != null) {
93167
try {
168+
final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration.get() != null ? mServiceConfiguration.get() : createAuthorizationServiceConfiguration(serviceConfiguration);
94169
authorizeWithConfiguration(
95-
createAuthorizationServiceConfiguration(serviceConfiguration),
170+
serviceConfig,
96171
appAuthConfiguration,
97172
clientId,
98173
scopes,
@@ -116,6 +191,8 @@ public void onFetchConfigurationCompleted(
116191
return;
117192
}
118193

194+
mServiceConfiguration.set(fetchedConfiguration);
195+
119196
authorizeWithConfiguration(
120197
fetchedConfiguration,
121198
appAuthConfiguration,
@@ -165,10 +242,11 @@ public void refresh(
165242
this.additionalParametersMap = additionalParametersMap;
166243

167244
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
168-
if (serviceConfiguration != null) {
245+
if (serviceConfiguration != null || mServiceConfiguration.get() != null) {
169246
try {
247+
final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration.get() != null ? mServiceConfiguration.get() : createAuthorizationServiceConfiguration(serviceConfiguration);
170248
refreshWithConfiguration(
171-
createAuthorizationServiceConfiguration(serviceConfiguration),
249+
serviceConfig,
172250
appAuthConfiguration,
173251
refreshToken,
174252
clientId,
@@ -184,7 +262,6 @@ public void refresh(
184262
}
185263
} else {
186264
final Uri issuerUri = Uri.parse(issuer);
187-
// @TODO: Refactor to avoid hitting IDP endpoint on refresh, reuse fetchedConfiguration if possible.
188265
AuthorizationServiceConfiguration.fetchFromUrl(
189266
buildConfigurationUriFromIssuer(issuerUri),
190267
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
@@ -196,6 +273,8 @@ public void onFetchConfigurationCompleted(
196273
return;
197274
}
198275

276+
mServiceConfiguration.set(fetchedConfiguration);
277+
199278
refreshWithConfiguration(
200279
fetchedConfiguration,
201280
appAuthConfiguration,
@@ -498,6 +577,21 @@ private AuthorizationServiceConfiguration createAuthorizationServiceConfiguratio
498577
);
499578
}
500579

580+
private void warmChromeCustomTab(Context context, final String issuer) {
581+
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
582+
@Override
583+
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
584+
client.warmup(0);
585+
client.newSession(new CustomTabsCallback()).mayLaunchUrl(Uri.parse(issuer), null, Collections.<Bundle>emptyList());
586+
}
587+
588+
@Override
589+
public void onServiceDisconnected(ComponentName name) {
590+
591+
}
592+
};
593+
CustomTabsClient.bindCustomTabsService(context, CUSTOM_TAB_PACKAGE_NAME, connection);
594+
}
501595

502596
@Override
503597
public void onNewIntent(Intent intent) {

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export interface RefreshConfiguration {
7070
refreshToken: string;
7171
}
7272

73+
export function prefetchConfiguration(config: AuthConfiguration): Promise<void>;
74+
7375
export function authorize(config: AuthConfiguration): Promise<AuthorizeResult>;
7476

7577
export function refresh(

index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,37 @@ const validateHeaders = headers => {
4848
});
4949
};
5050

51+
export const prefetchConfiguration = async ({
52+
warmAndPrefetchChrome,
53+
issuer,
54+
redirectUrl,
55+
clientId,
56+
scopes,
57+
serviceConfiguration,
58+
dangerouslyAllowInsecureHttpRequests = false,
59+
customHeaders,
60+
}) => {
61+
if (Platform.OS === 'android') {
62+
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
63+
validateClientId(clientId);
64+
validateRedirectUrl(redirectUrl);
65+
validateHeaders(customHeaders);
66+
67+
const nativeMethodArguments = [
68+
warmAndPrefetchChrome,
69+
issuer,
70+
redirectUrl,
71+
clientId,
72+
scopes,
73+
serviceConfiguration,
74+
dangerouslyAllowInsecureHttpRequests,
75+
customHeaders,
76+
];
77+
78+
RNAppAuth.prefetchConfiguration(...nativeMethodArguments);
79+
}
80+
};
81+
5182
export const authorize = ({
5283
issuer,
5384
redirectUrl,

0 commit comments

Comments
 (0)