Skip to content

Add usePKCE Flag for Dropbox #232

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 1, 2019
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
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ These providers implement the OAuth2 spec, but are not OpenID providers, which m

* [Uber](https://developer.uber.com/docs/deliveries/guides/three-legged-oauth) ([Example configuration](#uber))
* [Fitbit](https://dev.fitbit.com/build/reference/web-api/oauth2/) ([Example configuration](#fitbit))
* [Dropbox](https://www.dropbox.com/developers/reference/oauth-guide) ([Example configuration](#dropbox))

## Why you may want to use this library

Expand Down Expand Up @@ -97,14 +98,16 @@ with optional overrides.
`hello=world&foo=bar` to the authorization request.
* **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ whether to allow requests over plain HTTP or with self-signed SSL certificates. :warning: Can be useful for testing against local server, _should not be used in production._ This setting has no effect on iOS; to enable insecure HTTP requests, add a [NSExceptionAllowsInsecureHTTPLoads exception](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains) to your App Transport Security settings.
* **useNonce** - (`boolean`) _IOS_ (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers
* **usePKCE** - (`boolean`) _IOS_ (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.

#### result

This is the result from the auth server

* **accessToken** - (`string`) the access token
* **accessTokenExpirationDate** - (`string`) the token expiration date
* **additionalParameters** - (`Object`) additional url parameters from the auth server
* **authorizeAdditionalParameters** - (`Object`) additional url parameters from the authorizationEndpoint response.
* **tokenAdditionalParameters** - (`Object`) additional url parameters from the tokenEndpoint response.
* **idToken** - (`string`) the id token
* **refreshToken** - (`string`) the refresh token
* **tokenType** - (`string`) the token type, e.g. Bearer
Expand Down Expand Up @@ -714,6 +717,36 @@ await revoke(config, {
});
```

### Dropbox

Dropbox provides an OAuth 2.0 endpoint for logging in with a Dropbox user's credentials. You'll need to first [register your Dropbox application here](https://www.dropbox.com/developers/apps/create).

Please note:

* Dropbox does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead.
* Dropbox OAuth requires a [client secret](#note-about-client-secrets).
* Dropbox OAuth does not allow non-https redirect URLs, so you'll need to use a [Universal Link on iOS](https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html) or write a HTTPS endpoint.
* Dropbox OAuth does not provide refresh tokens or a revoke endpoint.

```js
const config = {
clientId: 'your-client-id-generated-by-dropbox',
clientSecret: 'your-client-secret-generated-by-dropbox',
redirectUrl: 'https://native-redirect-endpoint/oauth/dropbox',
scopes: [],
serviceConfiguration: {
authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
tokenEndpoint: `https://www.dropbox.com/oauth2/token`,
},
useNonce: false,
usePKCE: false,
};

// Log in to get an authentication token
const authState = await authorize(config);
const dropboxUID = authState.tokenAdditionalParameters.account_id;
```

## Contributors

Thanks goes to these wonderful people
Expand Down
15 changes: 9 additions & 6 deletions android/src/main/java/com/rnappauth/RNAppAuthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -447,29 +447,32 @@ private WritableMap tokenResponseToMap(TokenResponse response, AuthorizationResp
map.putString("accessTokenExpirationDate", expirationDateString);
}

WritableMap additionalParametersMap = Arguments.createMap();
WritableMap authorizeAdditionalParameters = Arguments.createMap();

if (!authResponse.additionalParameters.isEmpty()) {

Iterator<String> iterator = authResponse.additionalParameters.keySet().iterator();

while(iterator.hasNext()) {
String key = iterator.next();
additionalParametersMap.putString(key, authResponse.additionalParameters.get(key));
authorizeAdditionalParameters.putString(key, authResponse.additionalParameters.get(key));
}
}

if(!this.additionalParametersMap.isEmpty()) {
WritableMap tokenAdditionalParameters = Arguments.createMap();

Iterator<String> iterator = this.additionalParametersMap.keySet().iterator();
if (!response.additionalParameters.isEmpty()) {

Iterator<String> iterator = response.additionalParameters.keySet().iterator();

while(iterator.hasNext()) {
String key = iterator.next();
additionalParametersMap.putString(key, this.additionalParametersMap.get(key));
tokenAdditionalParameters.putString(key, response.additionalParameters.get(key));
}
}

map.putMap("additionalParameters", additionalParametersMap);
map.putMap("authorizeAdditionalParameters", authorizeAdditionalParameters);
map.putMap("tokenAdditionalParameters", tokenAdditionalParameters);
map.putString("idToken", response.idToken);
map.putString("refreshToken", response.refreshToken);
map.putString("tokenType", response.tokenType);
Expand Down
15 changes: 13 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,29 @@ export type AuthConfiguration = BaseAuthConfiguration & {
additionalParameters?: BuiltInParameters & { [name: string]: string };
dangerouslyAllowInsecureHttpRequests?: boolean;
useNonce?: boolean;
usePKCE?: boolean;
};

export interface AuthorizeResult {
accessToken: string;
accessTokenExpirationDate: string;
additionalParameters?: { [name: string]: string };
authorizeAdditionalParameters?: { [name: string]: string };
tokenAdditionalParameters?: { [name: string]: string };
idToken: string;
refreshToken: string;
tokenType: string;
scopes: [string];
}

export interface RefreshResult {
accessToken: string;
accessTokenExpirationDate: string;
additionalParameters?: { [name: string]: string };
idToken: string;
refreshToken: string;
tokenType: string;
}

export interface RevokeConfiguration {
tokenToRevoke: string;
sendClientId?: boolean;
Expand All @@ -56,7 +67,7 @@ export function authorize(config: AuthConfiguration): Promise<AuthorizeResult>;
export function refresh(
config: AuthConfiguration,
refreshConfig: RefreshConfiguration
): Promise<AuthorizeResult>;
): Promise<RefreshResult>;

export function revoke(
config: BaseAuthConfiguration,
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const authorize = ({
clientSecret,
scopes,
useNonce = true,
usePKCE = true,
additionalParameters,
serviceConfiguration,
dangerouslyAllowInsecureHttpRequests = false,
Expand All @@ -54,6 +55,7 @@ export const authorize = ({

if (Platform.OS === 'ios') {
nativeMethodArguments.push(useNonce);
nativeMethodArguments.push(usePKCE);
}

return RNAppAuth.authorize(...nativeMethodArguments);
Expand Down
42 changes: 41 additions & 1 deletion index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('AppAuth', () => {
serviceConfiguration: null,
scopes: ['my-scope'],
useNonce: true,
usePKCE: true,
};

describe('authorize', () => {
Expand Down Expand Up @@ -89,7 +90,8 @@ describe('AppAuth', () => {
config.scopes,
config.additionalParameters,
config.serviceConfiguration,
config.useNonce
config.useNonce,
config.usePKCE
);
});

Expand Down Expand Up @@ -286,6 +288,7 @@ describe('AppAuth', () => {
config.scopes,
config.additionalParameters,
config.serviceConfiguration,
true,
true
);
});
Expand All @@ -300,6 +303,43 @@ describe('AppAuth', () => {
config.scopes,
config.additionalParameters,
config.serviceConfiguration,
false,
true
);
});
});

describe('iOS-specific usePKCE parameter', () => {
beforeEach(() => {
require('react-native').Platform.OS = 'ios';
});

it('calls the native wrapper with default value `true`', () => {
authorize(config, { refreshToken: 'such-token' });
expect(mockAuthorize).toHaveBeenCalledWith(
config.issuer,
config.redirectUrl,
config.clientId,
config.clientSecret,
config.scopes,
config.additionalParameters,
config.serviceConfiguration,
config.useNonce,
true
);
});

it('calls the native wrapper with passed value `false`', () => {
authorize({ ...config, usePKCE: false }, { refreshToken: 'such-token' });
expect(mockAuthorize).toHaveBeenCalledWith(
config.issuer,
config.redirectUrl,
config.clientId,
config.clientSecret,
config.scopes,
config.additionalParameters,
config.serviceConfiguration,
config.useNonce,
false
);
});
Expand Down
13 changes: 9 additions & 4 deletions ios/RNAppAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ - (dispatch_queue_t)methodQueue
additionalParameters: (NSDictionary *_Nullable) additionalParameters
serviceConfiguration: (NSDictionary *_Nullable) serviceConfiguration
useNonce: (BOOL *) useNonce
usePKCE: (BOOL *) usePKCE
resolve: (RCTPromiseResolveBlock) resolve
reject: (RCTPromiseRejectBlock) reject)
{
Expand All @@ -51,6 +52,7 @@ - (dispatch_queue_t)methodQueue
clientSecret: clientSecret
scopes: scopes
useNonce: useNonce
usePKCE: usePKCE
additionalParameters: additionalParameters
resolve: resolve
reject: reject];
Expand All @@ -67,6 +69,7 @@ - (dispatch_queue_t)methodQueue
clientSecret: clientSecret
scopes: scopes
useNonce: useNonce
usePKCE: usePKCE
additionalParameters: additionalParameters
resolve: resolve
reject: reject];
Expand Down Expand Up @@ -165,13 +168,14 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
clientSecret: (NSString *) clientSecret
scopes: (NSArray *) scopes
useNonce: (BOOL *) useNonce
usePKCE: (BOOL *) usePKCE
additionalParameters: (NSDictionary *_Nullable) additionalParameters
resolve: (RCTPromiseResolveBlock) resolve
reject: (RCTPromiseRejectBlock) reject
{

NSString *codeVerifier = [[self class] generateCodeVerifier];
NSString *codeChallenge = [[self class] codeChallengeS256ForVerifier:codeVerifier];
NSString *codeVerifier = usePKCE ? [[self class] generateCodeVerifier] : nil;
NSString *codeChallenge = usePKCE ? [[self class] codeChallengeS256ForVerifier:codeVerifier] : nil;
NSString *nonce = useNonce ? [[self class] generateState] : nil;

// builds authentication request
Expand All @@ -186,7 +190,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
nonce:nonce
codeVerifier:codeVerifier
codeChallenge:codeChallenge
codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256
codeChallengeMethod: usePKCE ? OIDOAuthorizationRequestCodeChallengeMethodS256 : nil
additionalParameters:additionalParameters];

// performs authentication request
Expand Down Expand Up @@ -280,7 +284,8 @@ - (NSDictionary*)formatResponse: (OIDTokenResponse*) response

return @{@"accessToken": response.accessToken ? response.accessToken : @"",
@"accessTokenExpirationDate": response.accessTokenExpirationDate ? [dateFormat stringFromDate:response.accessTokenExpirationDate] : @"",
@"additionalParameters": authResponse.additionalParameters,
@"authorizeAdditionalParameters": authResponse.additionalParameters,
@"tokenAdditionalParameters": response.additionalParameters,
@"idToken": response.idToken ? response.idToken : @"",
@"refreshToken": response.refreshToken ? response.refreshToken : @"",
@"tokenType": response.tokenType ? response.tokenType : @"",
Expand Down