Skip to content

Commit a83ce89

Browse files
FelipeBuileskadikraman
authored andcommitted
Support making nonce nullable (#169)
* add a new useNonce param to authorize on iOS to support making nonce nullable * fix failing test * forgot to declare paramenter in the authorize call * update implementation according to maintainer from AppAuth to not depend on a change to their codebase * support additional parameters in the response * support sending back additional params on android * clean up the hack a bit * fix unexpected typo * fix unexpected typo reloaded
1 parent 69d2743 commit a83ce89

File tree

4 files changed

+120
-5
lines changed

4 files changed

+120
-5
lines changed

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public void onFetchConfigurationCompleted(
207207
@Override
208208
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
209209
if (requestCode == 0) {
210-
AuthorizationResponse response = AuthorizationResponse.fromIntent(data);
210+
final AuthorizationResponse response = AuthorizationResponse.fromIntent(data);
211211
AuthorizationException exception = AuthorizationException.fromIntent(data);
212212
if (exception != null) {
213213
promise.reject("RNAppAuth Error", "Failed to authenticate", exception);
@@ -229,7 +229,7 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
229229
public void onTokenRequestCompleted(
230230
TokenResponse resp, AuthorizationException ex) {
231231
if (resp != null) {
232-
WritableMap map = tokenResponseToMap(resp);
232+
WritableMap map = tokenResponseToMap(resp, response.additionalParameters);
233233
authorizePromise.resolve(map);
234234
} else {
235235
promise.reject("RNAppAuth Error", "Failed exchange token", ex);
@@ -394,7 +394,7 @@ private String arrayToString(ReadableArray array) {
394394
return strBuilder.toString();
395395
}
396396

397-
/*
397+
/*
398398
* Read raw token response into a React Native map to be passed down the bridge
399399
*/
400400
private WritableMap tokenResponseToMap(TokenResponse response) {
@@ -430,6 +430,52 @@ private WritableMap tokenResponseToMap(TokenResponse response) {
430430
return map;
431431
}
432432

433+
/*
434+
* Read raw token response into a React Native map to be passed down the bridge
435+
*/
436+
private WritableMap tokenResponseToMap(TokenResponse response, Map<String, String> responseAdditionalParameters) {
437+
WritableMap map = Arguments.createMap();
438+
439+
map.putString("accessToken", response.accessToken);
440+
441+
if (response.accessTokenExpirationTime != null) {
442+
Date expirationDate = new Date(response.accessTokenExpirationTime);
443+
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
444+
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
445+
String expirationDateString = formatter.format(expirationDate);
446+
map.putString("accessTokenExpirationDate", expirationDateString);
447+
}
448+
449+
WritableMap additionalParametersMap = Arguments.createMap();
450+
451+
if (!responseAdditionalParameters.isEmpty()) {
452+
453+
Iterator<String> iterator = responseAdditionalParameters.keySet().iterator();
454+
455+
while(iterator.hasNext()) {
456+
String key = iterator.next();
457+
additionalParametersMap.putString(key, responseAdditionalParameters.get(key));
458+
}
459+
}
460+
461+
if(!this.additionalParametersMap.isEmpty()) {
462+
463+
Iterator<String> iterator = this.additionalParametersMap.keySet().iterator();
464+
465+
while(iterator.hasNext()) {
466+
String key = iterator.next();
467+
additionalParametersMap.putString(key, this.additionalParametersMap.get(key));
468+
}
469+
}
470+
471+
map.putMap("additionalParameters", additionalParametersMap);
472+
map.putString("idToken", response.idToken);
473+
map.putString("refreshToken", response.refreshToken);
474+
map.putString("tokenType", response.tokenType);
475+
476+
return map;
477+
}
478+
433479
/*
434480
* Create an App Auth configuration using the provided connection builder
435481
*/

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const authorize = ({
2828
clientId,
2929
clientSecret,
3030
scopes,
31+
useNonce = true,
3132
additionalParameters,
3233
serviceConfiguration,
3334
dangerouslyAllowInsecureHttpRequests = false,
@@ -48,6 +49,10 @@ export const authorize = ({
4849
];
4950
if (Platform.OS === 'android') {
5051
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
52+
} else {
53+
// add a new useNonce param on iOS to support making it optional
54+
const nonceParamIndex = 5;
55+
nativeMethodArguments.splice(nonceParamIndex, 0, useNonce);
5156
}
5257

5358
return RNAppAuth.authorize(...nativeMethodArguments);

index.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ describe('AppAuth', () => {
3232
additionalParameters: { hello: 'world' },
3333
serviceConfiguration: null,
3434
scopes: ['my-scope'],
35+
useNonce: true,
3536
};
3637

3738
describe('authorize', () => {
@@ -86,6 +87,7 @@ describe('AppAuth', () => {
8687
config.clientId,
8788
config.clientSecret,
8889
config.scopes,
90+
config.useNonce,
8991
config.additionalParameters,
9092
config.serviceConfiguration
9193
);

ios/RNAppAuth.m

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ - (dispatch_queue_t)methodQueue
2020
return dispatch_get_main_queue();
2121
}
2222

23+
/*! @brief Number of random bytes generated for the @ state.
24+
*/
25+
static NSUInteger const kStateSizeBytes = 32;
26+
27+
/*! @brief Number of random bytes generated for the @ codeVerifier.
28+
*/
29+
static NSUInteger const kCodeVerifierBytes = 32;
30+
2331
RCT_EXPORT_MODULE()
2432

2533
RCT_REMAP_METHOD(authorize,
@@ -28,6 +36,7 @@ - (dispatch_queue_t)methodQueue
2836
clientId: (NSString *) clientId
2937
clientSecret: (NSString *) clientSecret
3038
scopes: (NSArray *) scopes
39+
useNonce: (BOOL *) useNonce
3140
additionalParameters: (NSDictionary *_Nullable) additionalParameters
3241
serviceConfiguration: (NSDictionary *_Nullable) serviceConfiguration
3342
resolve: (RCTPromiseResolveBlock) resolve
@@ -41,6 +50,7 @@ - (dispatch_queue_t)methodQueue
4150
clientId: clientId
4251
clientSecret: clientSecret
4352
scopes: scopes
53+
useNonce: useNonce
4454
additionalParameters: additionalParameters
4555
resolve: resolve
4656
reject: reject];
@@ -56,6 +66,7 @@ - (dispatch_queue_t)methodQueue
5666
clientId: clientId
5767
clientSecret: clientSecret
5868
scopes: scopes
69+
useNonce: useNonce
5970
additionalParameters: additionalParameters
6071
resolve: resolve
6172
reject: reject];
@@ -126,6 +137,25 @@ - (OIDServiceConfiguration *) createServiceConfiguration: (NSDictionary *) servi
126137
return configuration;
127138
}
128139

140+
+ (nullable NSString *)generateCodeVerifier {
141+
return [OIDTokenUtilities randomURLSafeStringWithSize:kCodeVerifierBytes];
142+
}
143+
144+
+ (nullable NSString *)generateState {
145+
return [OIDTokenUtilities randomURLSafeStringWithSize:kStateSizeBytes];
146+
}
147+
148+
+ (nullable NSString *)codeChallengeS256ForVerifier:(NSString *)codeVerifier {
149+
if (!codeVerifier) {
150+
return nil;
151+
}
152+
// generates the code_challenge per spec https://tools.ietf.org/html/rfc7636#section-4.2
153+
// code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
154+
// NB. the ASCII conversion on the code_verifier entropy was done at time of generation.
155+
NSData *sha256Verifier = [OIDTokenUtilities sha256:codeVerifier];
156+
return [OIDTokenUtilities encodeBase64urlNoPadding:sha256Verifier];
157+
}
158+
129159
/*
130160
* Authorize a user in exchange for a token with provided OIDServiceConfiguration
131161
*/
@@ -134,18 +164,29 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
134164
clientId: (NSString *) clientId
135165
clientSecret: (NSString *) clientSecret
136166
scopes: (NSArray *) scopes
167+
useNonce: (BOOL *) useNonce
137168
additionalParameters: (NSDictionary *_Nullable) additionalParameters
138169
resolve: (RCTPromiseResolveBlock) resolve
139170
reject: (RCTPromiseRejectBlock) reject
140171
{
172+
173+
NSString *codeVerifier = [[self class] generateCodeVerifier];
174+
NSString *codeChallenge = [[self class] codeChallengeS256ForVerifier:codeVerifier];
175+
NSString *nonce = useNonce ? [[self class] generateState] : nil;
176+
141177
// builds authentication request
142178
OIDAuthorizationRequest *request =
143179
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
144180
clientId:clientId
145181
clientSecret:clientSecret
146-
scopes:scopes
182+
scope:[OIDScopeUtilities scopesWithArray:scopes]
147183
redirectURL:[NSURL URLWithString:redirectUrl]
148184
responseType:OIDResponseTypeCode
185+
state:[[self class] generateState]
186+
nonce:nonce
187+
codeVerifier:codeVerifier
188+
codeChallenge:codeChallenge
189+
codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256
149190
additionalParameters:additionalParameters];
150191

151192
// performs authentication request
@@ -163,7 +204,8 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
163204
typeof(self) strongSelf = weakSelf;
164205
strongSelf->_currentSession = nil;
165206
if (authState) {
166-
resolve([self formatResponse:authState.lastTokenResponse]);
207+
resolve([self formatResponse:authState.lastTokenResponse
208+
withAdditionalParameters:authState.lastAuthorizationResponse.additionalParameters]);
167209
} else {
168210
reject(@"RNAppAuth Error", [error localizedDescription], error);
169211
}
@@ -225,4 +267,24 @@ - (NSDictionary*)formatResponse: (OIDTokenResponse*) response {
225267
};
226268
}
227269

270+
/*
271+
* Take raw OIDTokenResponse and additional paramaeters from an OIDAuthorizationResponse
272+
* and turn them into an extended token response format to pass to JavaScript caller
273+
*/
274+
- (NSDictionary*)formatResponse: (OIDTokenResponse*) response
275+
withAdditionalParameters:(NSDictionary*) params{
276+
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
277+
dateFormat.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"UTC"];
278+
[dateFormat setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
279+
[dateFormat setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
280+
281+
return @{@"accessToken": response.accessToken ? response.accessToken : @"",
282+
@"accessTokenExpirationDate": response.accessTokenExpirationDate ? [dateFormat stringFromDate:response.accessTokenExpirationDate] : @"",
283+
@"additionalParameters": params,
284+
@"idToken": response.idToken ? response.idToken : @"",
285+
@"refreshToken": response.refreshToken ? response.refreshToken : @"",
286+
@"tokenType": response.tokenType ? response.tokenType : @"",
287+
};
288+
}
289+
228290
@end

0 commit comments

Comments
 (0)