Skip to content

Commit 9476e11

Browse files
committed
Add client registration for Android
1 parent 76c3411 commit 9476e11

File tree

7 files changed

+334
-34
lines changed

7 files changed

+334
-34
lines changed

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ rules:
2424
prettier/prettier:
2525
- error
2626
- trailingComma: es5
27+
eqeqeq:
28+
- error
29+
- smart

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

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import com.rnappauth.utils.MapUtil;
2626
import com.rnappauth.utils.UnsafeConnectionBuilder;
27+
import com.rnappauth.utils.RegistrationResponseFactory;
2728
import com.rnappauth.utils.TokenResponseFactory;
2829
import com.rnappauth.utils.CustomConnectionBuilder;
2930

@@ -36,14 +37,18 @@
3637
import net.openid.appauth.ClientAuthentication;
3738
import net.openid.appauth.ClientSecretBasic;
3839
import net.openid.appauth.ClientSecretPost;
40+
import net.openid.appauth.RegistrationRequest;
41+
import net.openid.appauth.RegistrationResponse;
3942
import net.openid.appauth.ResponseTypeValues;
4043
import net.openid.appauth.TokenResponse;
4144
import net.openid.appauth.TokenRequest;
4245
import net.openid.appauth.connectivity.ConnectionBuilder;
4346
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
4447

48+
import java.util.ArrayList;
4549
import java.util.Collections;
4650
import java.util.HashMap;
51+
import java.util.List;
4752
import java.util.Map;
4853
import java.util.concurrent.atomic.AtomicReference;
4954
import java.util.concurrent.CountDownLatch;
@@ -56,6 +61,7 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
5661
private Promise promise;
5762
private Boolean dangerouslyAllowInsecureHttpRequests;
5863
private String clientAuthMethod = "basic";
64+
private Map<String, String> registrationRequestHeaders = null;
5965
private Map<String, String> authorizationRequestHeaders = null;
6066
private Map<String, String> tokenRequestHeaders = null;
6167
private Map<String, String> additionalParametersMap;
@@ -130,6 +136,75 @@ public void onFetchConfigurationCompleted(
130136
}
131137
}
132138

139+
@ReactMethod
140+
public void register(
141+
String issuer,
142+
final ReadableArray redirectUris,
143+
final ReadableArray responseTypes,
144+
final ReadableArray grantTypes,
145+
final String subjectType,
146+
final String tokenEndpointAuthMethod,
147+
final ReadableMap additionalParameters,
148+
final ReadableMap serviceConfiguration,
149+
final Boolean dangerouslyAllowInsecureHttpRequests,
150+
final ReadableMap headers,
151+
final Promise promise
152+
) {
153+
this.parseHeaderMap(headers);
154+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.registrationRequestHeaders);
155+
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder);
156+
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
157+
158+
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
159+
if (serviceConfiguration != null || mServiceConfiguration.get() != null) {
160+
try {
161+
final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration.get() != null ? mServiceConfiguration.get() : createAuthorizationServiceConfiguration(serviceConfiguration);
162+
registerWithConfiguration(
163+
serviceConfig,
164+
appAuthConfiguration,
165+
redirectUris,
166+
responseTypes,
167+
grantTypes,
168+
subjectType,
169+
tokenEndpointAuthMethod,
170+
additionalParametersMap,
171+
promise
172+
);
173+
} catch (Exception e) {
174+
promise.reject("Failed to refresh token", e.getMessage());
175+
}
176+
} else {
177+
final Uri issuerUri = Uri.parse(issuer);
178+
AuthorizationServiceConfiguration.fetchFromUrl(
179+
buildConfigurationUriFromIssuer(issuerUri),
180+
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
181+
public void onFetchConfigurationCompleted(
182+
@Nullable AuthorizationServiceConfiguration fetchedConfiguration,
183+
@Nullable AuthorizationException ex) {
184+
if (ex != null) {
185+
promise.reject("Failed to fetch configuration", getErrorMessage(ex));
186+
return;
187+
}
188+
189+
mServiceConfiguration.set(fetchedConfiguration);
190+
191+
registerWithConfiguration(
192+
fetchedConfiguration,
193+
appAuthConfiguration,
194+
redirectUris,
195+
responseTypes,
196+
grantTypes,
197+
subjectType,
198+
tokenEndpointAuthMethod,
199+
additionalParametersMap,
200+
promise
201+
);
202+
}
203+
},
204+
builder);
205+
}
206+
}
207+
133208
@ReactMethod
134209
public void authorize(
135210
String issuer,
@@ -150,10 +225,6 @@ public void authorize(
150225
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder);
151226
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
152227

153-
if (clientSecret != null) {
154-
additionalParametersMap.put("client_secret", clientSecret);
155-
}
156-
157228
// store args in private fields for later use in onActivityResult handler
158229
this.promise = promise;
159230
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;
@@ -345,6 +416,64 @@ public void onTokenRequestCompleted(
345416
}
346417
}
347418

419+
/*
420+
* Perform dynamic client registration with the provided configuration
421+
*/
422+
private void registerWithConfiguration(
423+
final AuthorizationServiceConfiguration serviceConfiguration,
424+
final AppAuthConfiguration appAuthConfiguration,
425+
final ReadableArray redirectUris,
426+
final ReadableArray responseTypes,
427+
final ReadableArray grantTypes,
428+
final String subjectType,
429+
final String tokenEndpointAuthMethod,
430+
final Map<String, String> additionalParametersMap,
431+
final Promise promise
432+
) {
433+
final Context context = this.reactContext;
434+
435+
AuthorizationService authService = new AuthorizationService(context, appAuthConfiguration);
436+
437+
RegistrationRequest.Builder registrationRequestBuilder =
438+
new RegistrationRequest.Builder(
439+
serviceConfiguration,
440+
arrayToUriList(redirectUris)
441+
)
442+
.setAdditionalParameters(additionalParametersMap);
443+
444+
if (responseTypes != null) {
445+
registrationRequestBuilder.setResponseTypeValues(arrayToList(responseTypes));
446+
}
447+
448+
if (grantTypes != null) {
449+
registrationRequestBuilder.setGrantTypeValues(arrayToList(grantTypes));
450+
}
451+
452+
if (subjectType != null) {
453+
registrationRequestBuilder.setSubjectType(subjectType);
454+
}
455+
456+
if (tokenEndpointAuthMethod != null) {
457+
registrationRequestBuilder.setTokenEndpointAuthenticationMethod(tokenEndpointAuthMethod);
458+
}
459+
460+
RegistrationRequest registrationRequest = registrationRequestBuilder.build();
461+
462+
AuthorizationService.RegistrationResponseCallback registrationResponseCallback = new AuthorizationService.RegistrationResponseCallback() {
463+
@Override
464+
public void onRegistrationRequestCompleted(@Nullable RegistrationResponse response, @Nullable AuthorizationException ex) {
465+
if (response != null) {
466+
WritableMap map = RegistrationResponseFactory.registrationResponseToMap(response);
467+
promise.resolve(map);
468+
} else {
469+
promise.reject("Failed to refresh token", getErrorMessage(ex));
470+
}
471+
}
472+
};
473+
474+
authService.performRegistrationRequest(registrationRequest, registrationResponseCallback);
475+
}
476+
348477
/*
349478
* Authorize user with the provided configuration
350479
*/
@@ -487,6 +616,9 @@ private void parseHeaderMap (ReadableMap headerMap) {
487616
if (headerMap == null) {
488617
return;
489618
}
619+
if (headerMap.hasKey("register") && headerMap.getType("register") == ReadableType.Map) {
620+
this.registrationRequestHeaders = MapUtil.readableMapToHashMap(headerMap.getMap("register"));
621+
}
490622
if (headerMap.hasKey("authorize") && headerMap.getType("authorize") == ReadableType.Map) {
491623
this.authorizationRequestHeaders = MapUtil.readableMapToHashMap(headerMap.getMap("authorize"));
492624
}
@@ -527,6 +659,28 @@ private String arrayToString(ReadableArray array) {
527659
return strBuilder.toString();
528660
}
529661

662+
/*
663+
* Create a string list from an array of strings
664+
*/
665+
private List<String> arrayToList(ReadableArray array) {
666+
ArrayList<String> list = new ArrayList<>();
667+
for (int i = 0; i < array.size(); i++) {
668+
list.add(array.getString(i));
669+
}
670+
return list;
671+
}
672+
673+
/*
674+
* Create a Uri list from an array of strings
675+
*/
676+
private List<Uri> arrayToUriList(ReadableArray array) {
677+
ArrayList<Uri> list = new ArrayList<>();
678+
for (int i = 0; i < array.size(); i++) {
679+
list.add(Uri.parse(array.getString(i)));
680+
}
681+
return list;
682+
}
683+
530684
/*
531685
* Create an App Auth configuration using the provided connection builder
532686
*/

android/src/main/java/com/rnappauth/utils/MapUtil.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import androidx.annotation.Nullable;
44

5+
import com.facebook.react.bridge.Arguments;
56
import com.facebook.react.bridge.ReadableMap;
67
import com.facebook.react.bridge.ReadableMapKeySetIterator;
8+
import com.facebook.react.bridge.WritableMap;
79

810
import java.util.HashMap;
11+
import java.util.Iterator;
12+
import java.util.Map;
913

1014
public class MapUtil {
1115

@@ -22,4 +26,20 @@ public static HashMap<String, String> readableMapToHashMap(@Nullable ReadableMap
2226

2327
return hashMap;
2428
}
29+
30+
public static final WritableMap createAdditionalParametersMap(Map<String, String> additionalParameters) {
31+
WritableMap additionalParametersMap = Arguments.createMap();
32+
33+
if (!additionalParameters.isEmpty()) {
34+
35+
Iterator<String> iterator = additionalParameters.keySet().iterator();
36+
37+
while(iterator.hasNext()) {
38+
String key = iterator.next();
39+
additionalParametersMap.putString(key, additionalParameters.get(key));
40+
}
41+
}
42+
43+
return additionalParametersMap;
44+
}
2545
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.rnappauth.utils;
2+
3+
import com.facebook.react.bridge.Arguments;
4+
import com.facebook.react.bridge.WritableMap;
5+
6+
import net.openid.appauth.RegistrationResponse;
7+
8+
public final class RegistrationResponseFactory {
9+
/*
10+
* Read raw registration response into a React Native map to be passed down the bridge
11+
*/
12+
public static final WritableMap registrationResponseToMap(RegistrationResponse response) {
13+
WritableMap map = Arguments.createMap();
14+
15+
map.putString("clientId", response.clientId);
16+
map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters));
17+
18+
if (response.clientIdIssuedAt != null) {
19+
map.putString("clientIdIssuedAt", DateUtil.formatTimestamp(response.clientIdIssuedAt));
20+
}
21+
22+
if (response.clientSecret != null) {
23+
map.putString("clientSecret", response.clientSecret);
24+
}
25+
26+
if (response.clientSecretExpiresAt != null) {
27+
map.putString("clientSecretExpiresAt", DateUtil.formatTimestamp(response.clientSecretExpiresAt));
28+
}
29+
30+
if (response.registrationAccessToken != null) {
31+
map.putString("registrationAccessToken", response.registrationAccessToken);
32+
}
33+
34+
if (response.registrationClientUri != null) {
35+
map.putString("registrationClientUri", response.registrationClientUri.toString());
36+
}
37+
38+
if (response.tokenEndpointAuthMethod != null) {
39+
map.putString("tokenEndpointAuthMethod", response.tokenEndpointAuthMethod);
40+
}
41+
42+
return map;
43+
}
44+
}

android/src/main/java/com/rnappauth/utils/TokenResponseFactory.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,7 @@
99
import net.openid.appauth.AuthorizationResponse;
1010
import net.openid.appauth.TokenResponse;
1111

12-
import java.util.Iterator;
13-
import java.util.Map;
14-
1512
public final class TokenResponseFactory {
16-
private static final WritableMap createAdditionalParametersMap(Map<String, String> additionalParameters) {
17-
WritableMap additionalParametersMap = Arguments.createMap();
18-
19-
if (!additionalParameters.isEmpty()) {
20-
21-
Iterator<String> iterator = additionalParameters.keySet().iterator();
22-
23-
while(iterator.hasNext()) {
24-
String key = iterator.next();
25-
additionalParametersMap.putString(key, additionalParameters.get(key));
26-
}
27-
}
28-
29-
return additionalParametersMap;
30-
}
3113

3214
private static final WritableArray createScopeArray(String scope) {
3315
WritableArray scopeArray = Arguments.createArray();
@@ -51,7 +33,7 @@ public static final WritableMap tokenResponseToMap(TokenResponse response) {
5133
WritableMap map = Arguments.createMap();
5234

5335
map.putString("accessToken", response.accessToken);
54-
map.putMap("additionalParameters", createAdditionalParametersMap(response.additionalParameters));
36+
map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters));
5537
map.putString("idToken", response.idToken);
5638
map.putString("refreshToken", response.refreshToken);
5739
map.putString("tokenType", response.tokenType);
@@ -70,9 +52,9 @@ public static final WritableMap tokenResponseToMap(TokenResponse response, Autho
7052
WritableMap map = Arguments.createMap();
7153

7254
map.putString("accessToken", response.accessToken);
73-
map.putMap("authorizeAdditionalParameters", createAdditionalParametersMap(authResponse.additionalParameters));
74-
map.putMap("tokenAdditionalParameters", createAdditionalParametersMap(response.additionalParameters));
75-
map.putMap("additionalParameters", createAdditionalParametersMap(response.additionalParameters)); // DEPRECATED
55+
map.putMap("authorizeAdditionalParameters", MapUtil.createAdditionalParametersMap(authResponse.additionalParameters));
56+
map.putMap("tokenAdditionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters));
57+
map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(response.additionalParameters)); // DEPRECATED
7658
map.putString("idToken", response.idToken);
7759
map.putString("refreshToken", response.refreshToken);
7860
map.putString("tokenType", response.tokenType);

0 commit comments

Comments
 (0)