Skip to content

Commit

Permalink
Return codeVerifier value when both skipCodeExchange=true and usePKCE…
Browse files Browse the repository at this point in the history
…=true (#604)

* Create codeVerifier in RNAppAuthModule and pass it down AppAuth-Android.  Return it in Authorize response only when `skipCodeExchange` == true.

* codeVerifier already created in RNAppAuth when `usePKCE` == true.  Added to `authorize` response conditionally when `skipCodeExchange` == true.

* Update `AuthorizeResult` type to have optional `codeVerifier` property.

* Update readme for `codeVerifier` that will now be returned if both `skipCodeExchange=true` and `usePKCE=true`
  • Loading branch information
davemurphysf authored Feb 13, 2021
1 parent ae09747 commit 669cbc5
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ This is the result from the auth server:
- **tokenType** - (`string`) the token type, e.g. Bearer
- **scopes** - ([`string`]) the scopes the user has agreed to be granted
- **authorizationCode** - (`string`) the authorization code (only if `skipCodeExchange=true`)
- **codeVerifier** - (`string`) the codeVerifier value used for the PKCE exchange (only if both `skipCodeExchange=true` and `usePKCE=true`)

### `refresh`

Expand Down
15 changes: 14 additions & 1 deletion android/src/main/java/com/rnappauth/RNAppAuthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.ClientSecretBasic;
import net.openid.appauth.ClientSecretPost;
import net.openid.appauth.CodeVerifierUtil;
import net.openid.appauth.RegistrationRequest;
import net.openid.appauth.RegistrationResponse;
import net.openid.appauth.ResponseTypeValues;
Expand All @@ -63,6 +64,8 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
private Promise promise;
private boolean dangerouslyAllowInsecureHttpRequests;
private Boolean skipCodeExchange;
private Boolean usePKCE;
private String codeVerifier;
private String clientAuthMethod = "basic";
private Map<String, String> registrationRequestHeaders = null;
private Map<String, String> authorizationRequestHeaders = null;
Expand Down Expand Up @@ -236,6 +239,7 @@ public void authorize(
this.clientSecret = clientSecret;
this.clientAuthMethod = clientAuthMethod;
this.skipCodeExchange = skipCodeExchange;
this.usePKCE = usePKCE;

// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
if (serviceConfiguration != null || hasServiceConfiguration(issuer)) {
Expand Down Expand Up @@ -408,7 +412,13 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
}

if (this.skipCodeExchange) {
WritableMap map = TokenResponseFactory.authorizationResponseToMap(response);
WritableMap map;
if (this.usePKCE && this.codeVerifier != null) {
map = TokenResponseFactory.authorizationCodeResponseToMap(response, this.codeVerifier);
} else {
map = TokenResponseFactory.authorizationResponseToMap(response);
}

if (promise != null) {
promise.resolve(map);
}
Expand Down Expand Up @@ -571,6 +581,9 @@ private void authorizeWithConfiguration(

if (!usePKCE) {
authRequestBuilder.setCodeVerifier(null);
} else {
this.codeVerifier = CodeVerifierUtil.generateRandomCodeVerifier();
authRequestBuilder.setCodeVerifier(this.codeVerifier);
}

AuthorizationRequest authRequest = authRequestBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,27 @@ public static final WritableMap authorizationResponseToMap(AuthorizationResponse

return map;
}

/*
* Read raw authorization into a React Native map with codeVerifier value added if present to be passed down the bridge
*/
public static final WritableMap authorizationCodeResponseToMap(AuthorizationResponse authResponse, String codeVerifier) {
WritableMap map = Arguments.createMap();
map.putString("authorizationCode", authResponse.authorizationCode);
map.putString("accessToken", authResponse.accessToken);
map.putMap("additionalParameters", MapUtil.createAdditionalParametersMap(authResponse.additionalParameters));
map.putString("idToken", authResponse.idToken);
map.putString("tokenType", authResponse.tokenType);
map.putArray("scopes", createScopeArray(authResponse.scope));

if (authResponse.accessTokenExpirationTime != null) {
map.putString("accessTokenExpirationTime", DateUtil.formatTimestamp(authResponse.accessTokenExpirationTime));
}

if (!TextUtils.isEmpty(codeVerifier)) {
map.putString("codeVerifier", codeVerifier);
}

return map;
}
}
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export interface AuthorizeResult {
tokenType: string;
scopes: string[];
authorizationCode: string;
codeVerifier?: string;
}

export interface RefreshResult {
Expand Down
19 changes: 16 additions & 3 deletions ios/RNAppAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
[UIApplication.sharedApplication endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
if (authorizationResponse) {
resolve([self formatAuthorizationResponse:authorizationResponse]);
resolve([self formatAuthorizationResponse:authorizationResponse withCodeVerifier:codeVerifier]);
} else {
reject([self getErrorCode: error defaultCode:@"authentication_failed"],
[error localizedDescription], error);
Expand Down Expand Up @@ -378,21 +378,34 @@ - (void)refreshWithConfiguration: (OIDServiceConfiguration *)configuration
/*
* Take raw OIDAuthorizationResponse and turn it to response format to pass to JavaScript caller
*/
- (NSDictionary*)formatAuthorizationResponse: (OIDAuthorizationResponse*) response {
- (NSDictionary *)formatAuthorizationResponse: (OIDAuthorizationResponse *) response withCodeVerifier: (NSString *) codeVerifier {
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
dateFormat.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"UTC"];
[dateFormat setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
[dateFormat setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];

return @{@"authorizationCode": response.authorizationCode ? response.authorizationCode : @"",
if (codeVerifier == nil) {
return @{@"authorizationCode": response.authorizationCode ? response.authorizationCode : @"",
@"state": response.state ? response.state : @"",
@"accessToken": response.accessToken ? response.accessToken : @"",
@"accessTokenExpirationDate": response.accessTokenExpirationDate ? [dateFormat stringFromDate:response.accessTokenExpirationDate] : @"",
@"tokenType": response.tokenType ? response.tokenType : @"",
@"idToken": response.idToken ? response.idToken : @"",
@"scopes": response.scope ? [response.scope componentsSeparatedByString:@" "] : [NSArray new],
@"additionalParameters": response.additionalParameters,
};
} else {
return @{@"authorizationCode": response.authorizationCode ? response.authorizationCode : @"",
@"state": response.state ? response.state : @"",
@"accessToken": response.accessToken ? response.accessToken : @"",
@"accessTokenExpirationDate": response.accessTokenExpirationDate ? [dateFormat stringFromDate:response.accessTokenExpirationDate] : @"",
@"tokenType": response.tokenType ? response.tokenType : @"",
@"idToken": response.idToken ? response.idToken : @"",
@"scopes": response.scope ? [response.scope componentsSeparatedByString:@" "] : [NSArray new],
@"additionalParameters": response.additionalParameters,
@"codeVerifier": codeVerifier
};
}
}

/*
Expand Down

0 comments on commit 669cbc5

Please sign in to comment.