Skip to content

Commit d63968d

Browse files
nihauxkadikraman
authored andcommitted
Feat/issue FormidableLabs#262 - allow to pass custom headers to oauth requests (FormidableLabs#263)
* fix error: need to upgrade gradle on build. * fix error: 404 for dependencies on build * custom connection builder. Allow to pass custom headers to oauth requests. * custom connection builder. Allow to pass custom headers to oauth requests. * sync prettier conf with eslint * call refresh with custom headers * user tokenRequestHeaders for refresh * update typescript defs * update readme * update typescript defs * simplify validateHeaders * remove Headers obj
1 parent 2985737 commit d63968d

File tree

10 files changed

+328
-97
lines changed

10 files changed

+328
-97
lines changed

.prettierrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"printWidth": 100,
33
"singleQuote": true,
4-
"semi": true
4+
"semi": true,
5+
"trailingComma": "es5"
56
}

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ with optional overrides.
9797
Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add
9898
`hello=world&foo=bar` to the authorization request.
9999
* **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.
100+
* **customHeaders** - (`object`) _ANDROID_ you can specify custom headers to pass during authorize request and/or token request.
101+
* **authorize** - (`{ [key: string]: value }`) headers to be passed during authorization request.
102+
* **token** - (`{ [key: string]: value }`) headers to be passed during token retrieval request.
100103
* **useNonce** - (`boolean`) _IOS_ (default: true) optionally allows not sending the nonce parameter, to support non-compliant providers
101104
* **usePKCE** - (`boolean`) _IOS_ (default: true) optionally allows not sending the code_challenge parameter and skipping PKCE code verification, to support non-compliant providers.
102105

android/build.gradle

+5-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ android {
3737
}
3838

3939
repositories {
40-
mavenCentral()
40+
maven {
41+
url 'https://maven.google.com/'
42+
name 'Google'
43+
}
44+
jcenter()
4145
}
4246

4347
dependencies {
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Thu Jan 31 18:04:04 GMT 2019
1+
#Tue Mar 05 21:11:49 CET 2019
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

android/gradlew

100644100755
File mode changed.

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

+37-6
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
import com.facebook.react.bridge.ReadableArray;
1616
import com.facebook.react.bridge.ReadableMap;
1717
import com.facebook.react.bridge.WritableMap;
18+
import com.facebook.react.bridge.ReadableType;
19+
1820
import com.rnappauth.utils.MapUtil;
1921
import com.rnappauth.utils.UnsafeConnectionBuilder;
2022
import com.rnappauth.utils.TokenResponseFactory;
23+
import com.rnappauth.utils.CustomConnectionBuilder;
2124

2225
import net.openid.appauth.AppAuthConfiguration;
2326
import net.openid.appauth.AuthorizationException;
@@ -41,6 +44,8 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
4144
private final ReactApplicationContext reactContext;
4245
private Promise promise;
4346
private Boolean dangerouslyAllowInsecureHttpRequests;
47+
private Map<String, String> authorizationRequestHeaders = null;
48+
private Map<String, String> tokenRequestHeaders = null;
4449
private Map<String, String> additionalParametersMap;
4550
private String clientSecret;
4651

@@ -60,9 +65,11 @@ public void authorize(
6065
final ReadableMap additionalParameters,
6166
final ReadableMap serviceConfiguration,
6267
final Boolean dangerouslyAllowInsecureHttpRequests,
68+
final ReadableMap headers,
6369
final Promise promise
6470
) {
65-
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
71+
this.parseHeaderMap(headers);
72+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.authorizationRequestHeaders);
6673
final AppAuthConfiguration appAuthConfiguration = this.createAppAuthConfiguration(builder);
6774
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
6875

@@ -133,9 +140,11 @@ public void refresh(
133140
final ReadableMap additionalParameters,
134141
final ReadableMap serviceConfiguration,
135142
final Boolean dangerouslyAllowInsecureHttpRequests,
143+
final ReadableMap headers,
136144
final Promise promise
137145
) {
138-
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
146+
this.parseHeaderMap(headers);
147+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders);
139148
final AppAuthConfiguration appAuthConfiguration = createAppAuthConfiguration(builder);
140149
final HashMap<String, String> additionalParametersMap = MapUtil.readableMapToHashMap(additionalParameters);
141150

@@ -211,7 +220,7 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
211220

212221
final Promise authorizePromise = this.promise;
213222
final AppAuthConfiguration configuration = createAppAuthConfiguration(
214-
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)
223+
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests, this.tokenRequestHeaders)
215224
);
216225

217226
AuthorizationService authService = new AuthorizationService(this.reactContext, configuration);
@@ -375,6 +384,19 @@ public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable
375384
}
376385
}
377386

387+
private void parseHeaderMap (ReadableMap headerMap) {
388+
if (headerMap == null) {
389+
return;
390+
}
391+
if (headerMap.hasKey("authorize") && headerMap.getType("authorize") == ReadableType.Map) {
392+
this.authorizationRequestHeaders = MapUtil.readableMapToHashMap(headerMap.getMap("authorize"));
393+
}
394+
if (headerMap.hasKey("token") && headerMap.getType("token") == ReadableType.Map) {
395+
this.tokenRequestHeaders = MapUtil.readableMapToHashMap(headerMap.getMap("token"));
396+
}
397+
398+
}
399+
378400
/*
379401
* Create a space-delimited string from an array
380402
*/
@@ -402,12 +424,21 @@ private AppAuthConfiguration createAppAuthConfiguration(ConnectionBuilder connec
402424
/*
403425
* Create appropriate connection builder based on provided settings
404426
*/
405-
private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) {
427+
private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections, Map<String, String> headers) {
428+
ConnectionBuilder proxiedBuilder;
429+
406430
if (allowInsecureConnections.equals(true)) {
407-
return UnsafeConnectionBuilder.INSTANCE;
431+
proxiedBuilder =UnsafeConnectionBuilder.INSTANCE;
432+
} else {
433+
proxiedBuilder = DefaultConnectionBuilder.INSTANCE;
434+
}
435+
436+
CustomConnectionBuilder customConnection = new CustomConnectionBuilder(proxiedBuilder);
437+
if (headers != null) {
438+
customConnection.setHeaders(headers);
408439
}
409440

410-
return DefaultConnectionBuilder.INSTANCE;
441+
return customConnection;
411442
}
412443

413444
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.rnappauth.utils;
2+
3+
/*
4+
* Copyright 2016 The AppAuth for Android Authors. All Rights Reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7+
* in compliance with the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software distributed under the
12+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
13+
* express or implied. See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
18+
import android.net.Uri;
19+
import android.support.annotation.NonNull;
20+
21+
import net.openid.appauth.connectivity.ConnectionBuilder;
22+
23+
import java.io.IOException;
24+
import java.net.HttpURLConnection;
25+
import java.util.Map;
26+
27+
28+
/**
29+
* An implementation of {@link ConnectionBuilder} that permits
30+
* to set custom headers on connection use to request endpoints.
31+
* Useful for non-spec compliant oauth providers.
32+
*/
33+
public final class CustomConnectionBuilder implements ConnectionBuilder {
34+
35+
private Map<String, String> headers = null;
36+
private ConnectionBuilder connectionBuilder;
37+
38+
public CustomConnectionBuilder(ConnectionBuilder connectionBuilderToUse) {
39+
connectionBuilder = connectionBuilderToUse;
40+
}
41+
42+
public void setHeaders (Map<String, String> headersToSet) {
43+
headers = headersToSet;
44+
}
45+
46+
@NonNull
47+
@Override
48+
public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
49+
HttpURLConnection conn = connectionBuilder.openConnection(uri);
50+
if (headers != null) {
51+
for (Map.Entry<String, String> header: headers.entrySet()) {
52+
conn.setRequestProperty(header.getKey(), header.getValue());
53+
}
54+
}
55+
56+
return conn;
57+
}
58+
}

index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@ interface BuiltInParameters {
2323
prompt?: 'consent' | 'login' | 'none' | 'select_account';
2424
}
2525

26+
type CustomHeaders = {
27+
authorize?: Record<string, string>;
28+
token?: Record<string, string>;
29+
};
30+
2631
export type AuthConfiguration = BaseAuthConfiguration & {
2732
clientSecret?: string;
2833
scopes: string[];
2934
redirectUrl: string;
3035
additionalParameters?: BuiltInParameters & { [name: string]: string };
3136
dangerouslyAllowInsecureHttpRequests?: boolean;
37+
customHeaders?: CustomHeaders;
3238
useNonce?: boolean;
3339
usePKCE?: boolean;
3440
};

index.js

+32
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@ const validateClientId = clientId =>
2222
const validateRedirectUrl = redirectUrl =>
2323
invariant(typeof redirectUrl === 'string', 'Config error: redirectUrl must be a string');
2424

25+
const validateHeaders = headers => {
26+
if (!headers) {
27+
return;
28+
}
29+
const customHeaderTypeErrorMessage =
30+
'Config error: customHeaders type must be { token?: { [key: string]: string }, authorize?: { [key: string]: string }}';
31+
32+
const authorizedKeys = ['token', 'authorize'];
33+
const keys = Object.keys(headers);
34+
const correctKeys = keys.filter(key => authorizedKeys.includes(key));
35+
invariant(
36+
keys.length <= authorizedKeys.length &&
37+
correctKeys.length > 0 &&
38+
correctKeys.length === keys.length,
39+
customHeaderTypeErrorMessage
40+
);
41+
42+
Object.values(headers).forEach(value => {
43+
invariant(typeof value === 'object', customHeaderTypeErrorMessage);
44+
invariant(
45+
Object.values(value).filter(key => typeof key !== 'string').length === 0,
46+
customHeaderTypeErrorMessage
47+
);
48+
});
49+
};
50+
2551
export const authorize = ({
2652
issuer,
2753
redirectUrl,
@@ -33,10 +59,12 @@ export const authorize = ({
3359
additionalParameters,
3460
serviceConfiguration,
3561
dangerouslyAllowInsecureHttpRequests = false,
62+
customHeaders,
3663
}) => {
3764
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
3865
validateClientId(clientId);
3966
validateRedirectUrl(redirectUrl);
67+
validateHeaders(customHeaders);
4068
// TODO: validateAdditionalParameters
4169

4270
const nativeMethodArguments = [
@@ -51,6 +79,7 @@ export const authorize = ({
5179

5280
if (Platform.OS === 'android') {
5381
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
82+
nativeMethodArguments.push(customHeaders);
5483
}
5584

5685
if (Platform.OS === 'ios') {
@@ -71,12 +100,14 @@ export const refresh = (
71100
additionalParameters,
72101
serviceConfiguration,
73102
dangerouslyAllowInsecureHttpRequests = false,
103+
customHeaders,
74104
},
75105
{ refreshToken }
76106
) => {
77107
validateIssuerOrServiceConfigurationEndpoints(issuer, serviceConfiguration);
78108
validateClientId(clientId);
79109
validateRedirectUrl(redirectUrl);
110+
validateHeaders(customHeaders);
80111
invariant(refreshToken, 'Please pass in a refresh token');
81112
// TODO: validateAdditionalParameters
82113

@@ -93,6 +124,7 @@ export const refresh = (
93124

94125
if (Platform.OS === 'android') {
95126
nativeMethodArguments.push(dangerouslyAllowInsecureHttpRequests);
127+
nativeMethodArguments.push(customHeaders);
96128
}
97129

98130
return RNAppAuth.refresh(...nativeMethodArguments);

0 commit comments

Comments
 (0)