Skip to content

Commit 0563f3b

Browse files
awmichelkadikraman
authored andcommitted
Add usePKCE Flag for Dropbox (#232)
* Add option to disable PKCE for non-compliant providers like Dropbox. * Update README.md and tests. * Return separate `additionalParameters` maps for /token and /authorize. * Add example configuration and notes for Dropbox.
1 parent 68560ca commit 0563f3b

File tree

6 files changed

+108
-14
lines changed

6 files changed

+108
-14
lines changed

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ These providers implement the OAuth2 spec, but are not OpenID providers, which m
3737

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

4142
## Why you may want to use this library
4243

@@ -97,14 +98,16 @@ with optional overrides.
9798
`hello=world&foo=bar` to the authorization request.
9899
* **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.
99100
* **useNonce** - (`boolean`) _IOS_ (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers
101+
* **usePKCE** - (`boolean`) _IOS_ (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.
100102

101103
#### result
102104

103105
This is the result from the auth server
104106

105107
* **accessToken** - (`string`) the access token
106108
* **accessTokenExpirationDate** - (`string`) the token expiration date
107-
* **additionalParameters** - (`Object`) additional url parameters from the auth server
109+
* **authorizeAdditionalParameters** - (`Object`) additional url parameters from the authorizationEndpoint response.
110+
* **tokenAdditionalParameters** - (`Object`) additional url parameters from the tokenEndpoint response.
108111
* **idToken** - (`string`) the id token
109112
* **refreshToken** - (`string`) the refresh token
110113
* **tokenType** - (`string`) the token type, e.g. Bearer
@@ -714,6 +717,36 @@ await revoke(config, {
714717
});
715718
```
716719

720+
### Dropbox
721+
722+
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).
723+
724+
Please note:
725+
726+
* Dropbox does not provide a OIDC discovery endpoint, so `serviceConfiguration` is used instead.
727+
* Dropbox OAuth requires a [client secret](#note-about-client-secrets).
728+
* 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.
729+
* Dropbox OAuth does not provide refresh tokens or a revoke endpoint.
730+
731+
```js
732+
const config = {
733+
clientId: 'your-client-id-generated-by-dropbox',
734+
clientSecret: 'your-client-secret-generated-by-dropbox',
735+
redirectUrl: 'https://native-redirect-endpoint/oauth/dropbox',
736+
scopes: [],
737+
serviceConfiguration: {
738+
authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
739+
tokenEndpoint: `https://www.dropbox.com/oauth2/token`,
740+
},
741+
useNonce: false,
742+
usePKCE: false,
743+
};
744+
745+
// Log in to get an authentication token
746+
const authState = await authorize(config);
747+
const dropboxUID = authState.tokenAdditionalParameters.account_id;
748+
```
749+
717750
## Contributors
718751

719752
Thanks goes to these wonderful people

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -447,29 +447,32 @@ private WritableMap tokenResponseToMap(TokenResponse response, AuthorizationResp
447447
map.putString("accessTokenExpirationDate", expirationDateString);
448448
}
449449

450-
WritableMap additionalParametersMap = Arguments.createMap();
450+
WritableMap authorizeAdditionalParameters = Arguments.createMap();
451451

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

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

456456
while(iterator.hasNext()) {
457457
String key = iterator.next();
458-
additionalParametersMap.putString(key, authResponse.additionalParameters.get(key));
458+
authorizeAdditionalParameters.putString(key, authResponse.additionalParameters.get(key));
459459
}
460460
}
461461

462-
if(!this.additionalParametersMap.isEmpty()) {
462+
WritableMap tokenAdditionalParameters = Arguments.createMap();
463463

464-
Iterator<String> iterator = this.additionalParametersMap.keySet().iterator();
464+
if (!response.additionalParameters.isEmpty()) {
465+
466+
Iterator<String> iterator = response.additionalParameters.keySet().iterator();
465467

466468
while(iterator.hasNext()) {
467469
String key = iterator.next();
468-
additionalParametersMap.putString(key, this.additionalParametersMap.get(key));
470+
tokenAdditionalParameters.putString(key, response.additionalParameters.get(key));
469471
}
470472
}
471473

472-
map.putMap("additionalParameters", additionalParametersMap);
474+
map.putMap("authorizeAdditionalParameters", authorizeAdditionalParameters);
475+
map.putMap("tokenAdditionalParameters", tokenAdditionalParameters);
473476
map.putString("idToken", response.idToken);
474477
map.putString("refreshToken", response.refreshToken);
475478
map.putString("tokenType", response.tokenType);

index.d.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,29 @@ export type AuthConfiguration = BaseAuthConfiguration & {
3030
additionalParameters?: BuiltInParameters & { [name: string]: string };
3131
dangerouslyAllowInsecureHttpRequests?: boolean;
3232
useNonce?: boolean;
33+
usePKCE?: boolean;
3334
};
3435

3536
export interface AuthorizeResult {
3637
accessToken: string;
3738
accessTokenExpirationDate: string;
38-
additionalParameters?: { [name: string]: string };
39+
authorizeAdditionalParameters?: { [name: string]: string };
40+
tokenAdditionalParameters?: { [name: string]: string };
3941
idToken: string;
4042
refreshToken: string;
4143
tokenType: string;
4244
scopes: [string];
4345
}
4446

47+
export interface RefreshResult {
48+
accessToken: string;
49+
accessTokenExpirationDate: string;
50+
additionalParameters?: { [name: string]: string };
51+
idToken: string;
52+
refreshToken: string;
53+
tokenType: string;
54+
}
55+
4556
export interface RevokeConfiguration {
4657
tokenToRevoke: string;
4758
sendClientId?: boolean;
@@ -56,7 +67,7 @@ export function authorize(config: AuthConfiguration): Promise<AuthorizeResult>;
5667
export function refresh(
5768
config: AuthConfiguration,
5869
refreshConfig: RefreshConfiguration
59-
): Promise<AuthorizeResult>;
70+
): Promise<RefreshResult>;
6071

6172
export function revoke(
6273
config: BaseAuthConfiguration,

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const authorize = ({
2929
clientSecret,
3030
scopes,
3131
useNonce = true,
32+
usePKCE = true,
3233
additionalParameters,
3334
serviceConfiguration,
3435
dangerouslyAllowInsecureHttpRequests = false,
@@ -54,6 +55,7 @@ export const authorize = ({
5455

5556
if (Platform.OS === 'ios') {
5657
nativeMethodArguments.push(useNonce);
58+
nativeMethodArguments.push(usePKCE);
5759
}
5860

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

index.spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('AppAuth', () => {
3333
serviceConfiguration: null,
3434
scopes: ['my-scope'],
3535
useNonce: true,
36+
usePKCE: true,
3637
};
3738

3839
describe('authorize', () => {
@@ -89,7 +90,8 @@ describe('AppAuth', () => {
8990
config.scopes,
9091
config.additionalParameters,
9192
config.serviceConfiguration,
92-
config.useNonce
93+
config.useNonce,
94+
config.usePKCE
9395
);
9496
});
9597

@@ -286,6 +288,7 @@ describe('AppAuth', () => {
286288
config.scopes,
287289
config.additionalParameters,
288290
config.serviceConfiguration,
291+
true,
289292
true
290293
);
291294
});
@@ -300,6 +303,43 @@ describe('AppAuth', () => {
300303
config.scopes,
301304
config.additionalParameters,
302305
config.serviceConfiguration,
306+
false,
307+
true
308+
);
309+
});
310+
});
311+
312+
describe('iOS-specific usePKCE parameter', () => {
313+
beforeEach(() => {
314+
require('react-native').Platform.OS = 'ios';
315+
});
316+
317+
it('calls the native wrapper with default value `true`', () => {
318+
authorize(config, { refreshToken: 'such-token' });
319+
expect(mockAuthorize).toHaveBeenCalledWith(
320+
config.issuer,
321+
config.redirectUrl,
322+
config.clientId,
323+
config.clientSecret,
324+
config.scopes,
325+
config.additionalParameters,
326+
config.serviceConfiguration,
327+
config.useNonce,
328+
true
329+
);
330+
});
331+
332+
it('calls the native wrapper with passed value `false`', () => {
333+
authorize({ ...config, usePKCE: false }, { refreshToken: 'such-token' });
334+
expect(mockAuthorize).toHaveBeenCalledWith(
335+
config.issuer,
336+
config.redirectUrl,
337+
config.clientId,
338+
config.clientSecret,
339+
config.scopes,
340+
config.additionalParameters,
341+
config.serviceConfiguration,
342+
config.useNonce,
303343
false
304344
);
305345
});

ios/RNAppAuth.m

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ - (dispatch_queue_t)methodQueue
3939
additionalParameters: (NSDictionary *_Nullable) additionalParameters
4040
serviceConfiguration: (NSDictionary *_Nullable) serviceConfiguration
4141
useNonce: (BOOL *) useNonce
42+
usePKCE: (BOOL *) usePKCE
4243
resolve: (RCTPromiseResolveBlock) resolve
4344
reject: (RCTPromiseRejectBlock) reject)
4445
{
@@ -51,6 +52,7 @@ - (dispatch_queue_t)methodQueue
5152
clientSecret: clientSecret
5253
scopes: scopes
5354
useNonce: useNonce
55+
usePKCE: usePKCE
5456
additionalParameters: additionalParameters
5557
resolve: resolve
5658
reject: reject];
@@ -67,6 +69,7 @@ - (dispatch_queue_t)methodQueue
6769
clientSecret: clientSecret
6870
scopes: scopes
6971
useNonce: useNonce
72+
usePKCE: usePKCE
7073
additionalParameters: additionalParameters
7174
resolve: resolve
7275
reject: reject];
@@ -165,13 +168,14 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
165168
clientSecret: (NSString *) clientSecret
166169
scopes: (NSArray *) scopes
167170
useNonce: (BOOL *) useNonce
171+
usePKCE: (BOOL *) usePKCE
168172
additionalParameters: (NSDictionary *_Nullable) additionalParameters
169173
resolve: (RCTPromiseResolveBlock) resolve
170174
reject: (RCTPromiseRejectBlock) reject
171175
{
172176

173-
NSString *codeVerifier = [[self class] generateCodeVerifier];
174-
NSString *codeChallenge = [[self class] codeChallengeS256ForVerifier:codeVerifier];
177+
NSString *codeVerifier = usePKCE ? [[self class] generateCodeVerifier] : nil;
178+
NSString *codeChallenge = usePKCE ? [[self class] codeChallengeS256ForVerifier:codeVerifier] : nil;
175179
NSString *nonce = useNonce ? [[self class] generateState] : nil;
176180

177181
// builds authentication request
@@ -186,7 +190,7 @@ - (void)authorizeWithConfiguration: (OIDServiceConfiguration *) configuration
186190
nonce:nonce
187191
codeVerifier:codeVerifier
188192
codeChallenge:codeChallenge
189-
codeChallengeMethod:OIDOAuthorizationRequestCodeChallengeMethodS256
193+
codeChallengeMethod: usePKCE ? OIDOAuthorizationRequestCodeChallengeMethodS256 : nil
190194
additionalParameters:additionalParameters];
191195

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

281285
return @{@"accessToken": response.accessToken ? response.accessToken : @"",
282286
@"accessTokenExpirationDate": response.accessTokenExpirationDate ? [dateFormat stringFromDate:response.accessTokenExpirationDate] : @"",
283-
@"additionalParameters": authResponse.additionalParameters,
287+
@"authorizeAdditionalParameters": authResponse.additionalParameters,
288+
@"tokenAdditionalParameters": response.additionalParameters,
284289
@"idToken": response.idToken ? response.idToken : @"",
285290
@"refreshToken": response.refreshToken ? response.refreshToken : @"",
286291
@"tokenType": response.tokenType ? response.tokenType : @"",

0 commit comments

Comments
 (0)