Skip to content

Commit 2e17c94

Browse files
authored
Merge pull request #50 from FormidableLabs/feature/allow-insecure-requests-v2
Allow insecure HTTP requests on Android
2 parents 898cf39 + b95a758 commit 2e17c94

File tree

7 files changed

+341
-26
lines changed

7 files changed

+341
-26
lines changed

Example/ios/AppAuthExample/Info.plist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
<dict>
4040
<key>NSExceptionDomains</key>
4141
<dict>
42+
<key>demo.identityserver.io</key>
43+
<dict>
44+
<key>NSExceptionAllowsInsecureHTTPLoads</key>
45+
<true/>
46+
</dict>
4247
<key>localhost</key>
4348
<dict>
4449
<key>NSExceptionAllowsInsecureHTTPLoads</key>

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ with optional overrides.
7979
* **additionalParameters** - (`object`) additional parameters that will be passed in the authorization request.
8080
Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add
8181
`hello=world&foo=bar` to the authorization request.
82+
* :warning: **dangerouslyAllowInsecureHttpRequests** - (`boolean`) _ANDROID_ whether to allow requests over plain HTTP or with self-signed SSL certificates. 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.
8283

8384
#### result
8485

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

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.Context;
55
import android.content.Intent;
66
import android.net.Uri;
7+
import android.support.annotation.NonNull;
78
import android.support.annotation.Nullable;
89

910
import com.facebook.react.bridge.ActivityEventListener;
@@ -16,25 +17,37 @@
1617
import com.facebook.react.bridge.ReadableMap;
1718
import com.facebook.react.bridge.ReadableMapKeySetIterator;
1819
import com.facebook.react.bridge.WritableMap;
20+
import com.reactlibrary.utils.UnsafeConnectionBuilder;
1921

22+
import net.openid.appauth.AppAuthConfiguration;
2023
import net.openid.appauth.AuthorizationException;
2124
import net.openid.appauth.AuthorizationRequest;
2225
import net.openid.appauth.AuthorizationResponse;
2326
import net.openid.appauth.AuthorizationService;
2427
import net.openid.appauth.AuthorizationServiceConfiguration;
28+
import net.openid.appauth.Preconditions;
2529
import net.openid.appauth.ResponseTypeValues;
2630
import net.openid.appauth.TokenResponse;
2731
import net.openid.appauth.TokenRequest;
28-
32+
import net.openid.appauth.connectivity.ConnectionBuilder;
33+
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
34+
35+
import java.io.IOException;
36+
import java.net.HttpURLConnection;
37+
import java.net.MalformedURLException;
38+
import java.net.URL;
39+
import java.sql.Connection;
2940
import java.text.SimpleDateFormat;
3041
import java.util.Date;
3142
import java.util.HashMap;
3243
import java.util.Iterator;
44+
import java.util.concurrent.TimeUnit;
3345

3446
public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener {
3547

3648
private final ReactApplicationContext reactContext;
3749
private Promise promise;
50+
private Boolean dangerouslyAllowInsecureHttpRequests;
3851

3952
public RNAppAuthModule(ReactApplicationContext reactContext) {
4053
super(reactContext);
@@ -96,24 +109,53 @@ private HashMap<String, String> additionalParametersToMap(ReadableMap additional
96109
return additionalParametersHash;
97110
}
98111

112+
private AppAuthConfiguration createAppAuthConfiguration(ConnectionBuilder connectionBuilder) {
113+
return new AppAuthConfiguration
114+
.Builder()
115+
.setConnectionBuilder(connectionBuilder)
116+
.build();
117+
}
118+
119+
private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) {
120+
if (allowInsecureConnections.equals(true)) {
121+
return UnsafeConnectionBuilder.INSTANCE;
122+
}
123+
124+
return DefaultConnectionBuilder.INSTANCE;
125+
}
126+
127+
private Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) {
128+
return openIdConnectIssuerUri.buildUpon()
129+
.appendPath(AuthorizationServiceConfiguration.WELL_KNOWN_PATH)
130+
.appendPath(AuthorizationServiceConfiguration.OPENID_CONFIGURATION_RESOURCE)
131+
.build();
132+
}
133+
99134
@ReactMethod
100135
public void authorize(
101136
String issuer,
102137
final String redirectUrl,
103138
final String clientId,
104139
final ReadableArray scopes,
105140
final ReadableMap additionalParameters,
141+
final Boolean dangerouslyAllowInsecureHttpRequests,
106142
final Promise promise
107143
) {
108144

109145
final Context context = this.reactContext;
146+
147+
// store args in private fields for later use in onActivityResult handler
110148
this.promise = promise;
111-
final Activity currentActivity = getCurrentActivity();
149+
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;
112150

151+
final Activity currentActivity = getCurrentActivity();
113152
final String scopesString = this.arrayToString(scopes);
153+
final Uri issuerUri = Uri.parse(issuer);
154+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
155+
final AppAuthConfiguration configuration = this.createAppAuthConfiguration(builder);
114156

115-
AuthorizationServiceConfiguration.fetchFromIssuer(
116-
Uri.parse(issuer),
157+
AuthorizationServiceConfiguration.fetchFromUrl(
158+
buildConfigurationUriFromIssuer(issuerUri),
117159
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
118160
public void onFetchConfigurationCompleted(
119161
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
@@ -123,6 +165,7 @@ public void onFetchConfigurationCompleted(
123165
return;
124166
}
125167

168+
126169
AuthorizationRequest.Builder authRequestBuilder =
127170
new AuthorizationRequest.Builder(
128171
serviceConfiguration,
@@ -137,13 +180,14 @@ public void onFetchConfigurationCompleted(
137180
}
138181

139182
AuthorizationRequest authRequest = authRequestBuilder.build();
140-
141-
AuthorizationService authService = new AuthorizationService(context);
183+
AuthorizationService authService = new AuthorizationService(context, configuration);
142184
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
143185
currentActivity.startActivityForResult(authIntent, 0);
144186

145187
}
146-
});
188+
},
189+
builder
190+
);
147191

148192
}
149193

@@ -155,14 +199,20 @@ public void refresh(
155199
final String refreshToken,
156200
final ReadableArray scopes,
157201
final ReadableMap additionalParameters,
202+
final Boolean dangerouslyAllowInsecureHttpRequests,
158203
final Promise promise
159204
) {
160205
final Context context = this.reactContext;
161-
162206
final String scopesString = this.arrayToString(scopes);
207+
final Uri issuerUri = Uri.parse(issuer);
208+
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
209+
final AppAuthConfiguration configuration = createAppAuthConfiguration(builder);
163210

164-
AuthorizationServiceConfiguration.fetchFromIssuer(
165-
Uri.parse(issuer),
211+
// store setting in private field for later use in onActivityResult handler
212+
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;
213+
214+
AuthorizationServiceConfiguration.fetchFromUrl(
215+
buildConfigurationUriFromIssuer(issuerUri),
166216
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
167217
public void onFetchConfigurationCompleted(
168218
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
@@ -187,9 +237,7 @@ public void onFetchConfigurationCompleted(
187237

188238
TokenRequest tokenRequest = tokenRequestBuilder.build();
189239

190-
191-
AuthorizationService authService = new AuthorizationService(context);
192-
240+
AuthorizationService authService = new AuthorizationService(context, configuration);
193241
authService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() {
194242
@Override
195243
public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) {
@@ -203,7 +251,8 @@ public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable
203251
});
204252

205253
}
206-
});
254+
},
255+
builder);
207256
}
208257

209258
@Override
@@ -217,9 +266,11 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
217266
}
218267

219268
final Promise authorizePromise = this.promise;
269+
final AppAuthConfiguration configuration = createAppAuthConfiguration(
270+
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)
271+
);
220272

221-
AuthorizationService authService = new AuthorizationService(this.reactContext);
222-
273+
AuthorizationService authService = new AuthorizationService(this.reactContext, configuration);
223274
authService.performTokenRequest(
224275
response.createTokenExchangeRequest(),
225276
new AuthorizationService.TokenResponseCallback() {
@@ -248,4 +299,4 @@ public void onNewIntent(Intent intent) {
248299
public String getName() {
249300
return "RNAppAuth";
250301
}
251-
}
302+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.reactlibrary.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.annotation.SuppressLint;
19+
import android.net.Uri;
20+
import android.support.annotation.NonNull;
21+
import android.support.annotation.Nullable;
22+
import android.util.Log;
23+
24+
import net.openid.appauth.Preconditions;
25+
import net.openid.appauth.connectivity.ConnectionBuilder;
26+
27+
import java.io.IOException;
28+
import java.net.HttpURLConnection;
29+
import java.net.URL;
30+
import java.security.KeyManagementException;
31+
import java.security.NoSuchAlgorithmException;
32+
import java.security.cert.X509Certificate;
33+
import java.util.concurrent.TimeUnit;
34+
35+
import javax.net.ssl.HostnameVerifier;
36+
import javax.net.ssl.HttpsURLConnection;
37+
import javax.net.ssl.SSLContext;
38+
import javax.net.ssl.SSLSession;
39+
import javax.net.ssl.TrustManager;
40+
import javax.net.ssl.X509TrustManager;
41+
42+
/**
43+
* An implementation of {@link ConnectionBuilder} that permits connecting to http
44+
* links, and ignores certificates for https connections. *THIS SHOULD NOT BE USED IN PRODUCTION
45+
* CODE*. It is intended to facilitate easier testing of AppAuth against development servers
46+
* only.
47+
*/
48+
public final class UnsafeConnectionBuilder implements ConnectionBuilder {
49+
50+
public static final UnsafeConnectionBuilder INSTANCE = new UnsafeConnectionBuilder();
51+
52+
private static final String TAG = "ConnBuilder";
53+
54+
private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15);
55+
private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
56+
57+
private static final String HTTP = "http";
58+
private static final String HTTPS = "https";
59+
60+
@SuppressLint("TrustAllX509TrustManager")
61+
private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] {
62+
new X509TrustManager() {
63+
public X509Certificate[] getAcceptedIssuers() {
64+
return null;
65+
}
66+
67+
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
68+
69+
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
70+
}
71+
};
72+
73+
@SuppressLint("BadHostnameVerifier")
74+
private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() {
75+
public boolean verify(String hostname, SSLSession session) {
76+
return true;
77+
}
78+
};
79+
80+
@Nullable
81+
private static final SSLContext TRUSTING_CONTEXT;
82+
83+
static {
84+
SSLContext context;
85+
try {
86+
context = SSLContext.getInstance("SSL");
87+
} catch (NoSuchAlgorithmException e) {
88+
Log.e("ConnBuilder", "Unable to acquire SSL context");
89+
context = null;
90+
}
91+
92+
SSLContext initializedContext = null;
93+
if (context != null) {
94+
try {
95+
context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom());
96+
initializedContext = context;
97+
} catch (KeyManagementException e) {
98+
Log.e(TAG, "Failed to initialize trusting SSL context");
99+
}
100+
}
101+
102+
TRUSTING_CONTEXT = initializedContext;
103+
}
104+
105+
private UnsafeConnectionBuilder() {
106+
// no need to construct new instances
107+
}
108+
109+
@NonNull
110+
@Override
111+
public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
112+
Preconditions.checkNotNull(uri, "url must not be null");
113+
Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()),
114+
"scheme or uri must be http or https");
115+
HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection();
116+
conn.setConnectTimeout(CONNECTION_TIMEOUT_MS);
117+
conn.setReadTimeout(READ_TIMEOUT_MS);
118+
conn.setInstanceFollowRedirects(false);
119+
120+
if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) {
121+
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
122+
httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory());
123+
httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER);
124+
}
125+
126+
return conn;
127+
}
128+
}

index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface AuthConfiguration extends BaseAuthConfiguration {
66
scopes: string[];
77
redirectUrl: string;
88
additionalParameters?: { [name: string]: string };
9+
dangerouslyAllowInsecureHttpRequests?: boolean;
910
}
1011

1112
export interface RevokeConfiguration {

0 commit comments

Comments
 (0)