Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -396,7 +400,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 @@ -559,6 +569,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