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
5 changes: 5 additions & 0 deletions Example/ios/AppAuthExample/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>demo.identityserver.io</key>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what need to be done on iOS in order to support insecure connections? If so, should we add a comment to the readme on it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, could do that. The library already gives you a very informative warning saying the App Transport Security settings needs to be changed to allow HTTP connections. But no harm putting it in the README to save folks some cycles. Will amend 👍

<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ with optional overrides.
* **additionalParameters** - (`object`) additional parameters that will be passed in the authorization request.
Must be string values! E.g. setting `additionalParameters: { hello: 'world', foo: 'bar' }` would add
`hello=world&foo=bar` to the authorization request.
* :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.

#### result

Expand Down
85 changes: 68 additions & 17 deletions android/src/main/java/com/reactlibrary/RNAppAuthModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.facebook.react.bridge.ActivityEventListener;
Expand All @@ -16,25 +17,37 @@
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
import com.reactlibrary.utils.UnsafeConnectionBuilder;

import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.Preconditions;
import net.openid.appauth.ResponseTypeValues;
import net.openid.appauth.TokenResponse;
import net.openid.appauth.TokenRequest;

import net.openid.appauth.connectivity.ConnectionBuilder;
import net.openid.appauth.connectivity.DefaultConnectionBuilder;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener {

private final ReactApplicationContext reactContext;
private Promise promise;
private Boolean dangerouslyAllowInsecureHttpRequests;

public RNAppAuthModule(ReactApplicationContext reactContext) {
super(reactContext);
Expand Down Expand Up @@ -96,24 +109,53 @@ private HashMap<String, String> additionalParametersToMap(ReadableMap additional
return additionalParametersHash;
}

private AppAuthConfiguration createAppAuthConfiguration(ConnectionBuilder connectionBuilder) {
return new AppAuthConfiguration
.Builder()
.setConnectionBuilder(connectionBuilder)
.build();
}

private ConnectionBuilder createConnectionBuilder(Boolean allowInsecureConnections) {
if (allowInsecureConnections.equals(true)) {
return UnsafeConnectionBuilder.INSTANCE;
}

return DefaultConnectionBuilder.INSTANCE;
}

private Uri buildConfigurationUriFromIssuer(Uri openIdConnectIssuerUri) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are now using fetchFromUrl instead of fetchFromIssuer, we need to construct the issuer WKID endpoint ourselves. Annoyingly the method on AuthorizationServiceConfiguration was private, so I replicated it here. This should be a very stable method, since the URL format is driven by spec and not likely to change.

return openIdConnectIssuerUri.buildUpon()
.appendPath(AuthorizationServiceConfiguration.WELL_KNOWN_PATH)
.appendPath(AuthorizationServiceConfiguration.OPENID_CONFIGURATION_RESOURCE)
.build();
}

@ReactMethod
public void authorize(
String issuer,
final String redirectUrl,
final String clientId,
final ReadableArray scopes,
final ReadableMap additionalParameters,
final Boolean dangerouslyAllowInsecureHttpRequests,
final Promise promise
) {

final Context context = this.reactContext;

// store args in private fields for later use in onActivityResult handler
this.promise = promise;
final Activity currentActivity = getCurrentActivity();
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;

final Activity currentActivity = getCurrentActivity();
final String scopesString = this.arrayToString(scopes);
final Uri issuerUri = Uri.parse(issuer);
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
final AppAuthConfiguration configuration = this.createAppAuthConfiguration(builder);

AuthorizationServiceConfiguration.fetchFromIssuer(
Uri.parse(issuer),
AuthorizationServiceConfiguration.fetchFromUrl(
buildConfigurationUriFromIssuer(issuerUri),
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
public void onFetchConfigurationCompleted(
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
Expand All @@ -123,6 +165,7 @@ public void onFetchConfigurationCompleted(
return;
}


AuthorizationRequest.Builder authRequestBuilder =
new AuthorizationRequest.Builder(
serviceConfiguration,
Expand All @@ -137,13 +180,14 @@ public void onFetchConfigurationCompleted(
}

AuthorizationRequest authRequest = authRequestBuilder.build();

AuthorizationService authService = new AuthorizationService(context);
AuthorizationService authService = new AuthorizationService(context, configuration);
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
currentActivity.startActivityForResult(authIntent, 0);

}
});
},
builder
);

}

Expand All @@ -155,14 +199,20 @@ public void refresh(
final String refreshToken,
final ReadableArray scopes,
final ReadableMap additionalParameters,
final Boolean dangerouslyAllowInsecureHttpRequests,
final Promise promise
) {
final Context context = this.reactContext;

final String scopesString = this.arrayToString(scopes);
final Uri issuerUri = Uri.parse(issuer);
final ConnectionBuilder builder = createConnectionBuilder(dangerouslyAllowInsecureHttpRequests);
final AppAuthConfiguration configuration = createAppAuthConfiguration(builder);

AuthorizationServiceConfiguration.fetchFromIssuer(
Uri.parse(issuer),
// store setting in private field for later use in onActivityResult handler
this.dangerouslyAllowInsecureHttpRequests = dangerouslyAllowInsecureHttpRequests;

AuthorizationServiceConfiguration.fetchFromUrl(
buildConfigurationUriFromIssuer(issuerUri),
new AuthorizationServiceConfiguration.RetrieveConfigurationCallback() {
public void onFetchConfigurationCompleted(
@Nullable AuthorizationServiceConfiguration serviceConfiguration,
Expand All @@ -187,9 +237,7 @@ public void onFetchConfigurationCompleted(

TokenRequest tokenRequest = tokenRequestBuilder.build();


AuthorizationService authService = new AuthorizationService(context);

AuthorizationService authService = new AuthorizationService(context, configuration);
authService.performTokenRequest(tokenRequest, new AuthorizationService.TokenResponseCallback() {
@Override
public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable AuthorizationException ex) {
Expand All @@ -203,7 +251,8 @@ public void onTokenRequestCompleted(@Nullable TokenResponse response, @Nullable
});

}
});
},
builder);
}

@Override
Expand All @@ -217,9 +266,11 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
}

final Promise authorizePromise = this.promise;
final AppAuthConfiguration configuration = createAppAuthConfiguration(
createConnectionBuilder(this.dangerouslyAllowInsecureHttpRequests)
);

AuthorizationService authService = new AuthorizationService(this.reactContext);

AuthorizationService authService = new AuthorizationService(this.reactContext, configuration);
authService.performTokenRequest(
response.createTokenExchangeRequest(),
new AuthorizationService.TokenResponseCallback() {
Expand Down Expand Up @@ -248,4 +299,4 @@ public void onNewIntent(Intent intent) {
public String getName() {
return "RNAppAuth";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.reactlibrary.utils;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is copied as-is from the AppAuth Android example application. Only modification here is that I changed the name from ConnectionBuilderForTesting to UnsafeConnectionBuilder https://github.com/openid/AppAuth-Android/blob/29329dce430edebdc80e9635895358e20971f286/app/java/net/openid/appauthdemo/ConnectionBuilderForTesting.java


/*
* Copyright 2016 The AppAuth for Android Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/


import android.annotation.SuppressLint;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import net.openid.appauth.Preconditions;
import net.openid.appauth.connectivity.ConnectionBuilder;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

/**
* An implementation of {@link ConnectionBuilder} that permits connecting to http
* links, and ignores certificates for https connections. *THIS SHOULD NOT BE USED IN PRODUCTION
* CODE*. It is intended to facilitate easier testing of AppAuth against development servers
* only.
*/
public final class UnsafeConnectionBuilder implements ConnectionBuilder {

public static final UnsafeConnectionBuilder INSTANCE = new UnsafeConnectionBuilder();

private static final String TAG = "ConnBuilder";

private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15);
private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);

private static final String HTTP = "http";
private static final String HTTPS = "https";

@SuppressLint("TrustAllX509TrustManager")
private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(X509Certificate[] certs, String authType) {}

public void checkServerTrusted(X509Certificate[] certs, String authType) {}
}
};

@SuppressLint("BadHostnameVerifier")
private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};

@Nullable
private static final SSLContext TRUSTING_CONTEXT;

static {
SSLContext context;
try {
context = SSLContext.getInstance("SSL");
} catch (NoSuchAlgorithmException e) {
Log.e("ConnBuilder", "Unable to acquire SSL context");
context = null;
}

SSLContext initializedContext = null;
if (context != null) {
try {
context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom());
initializedContext = context;
} catch (KeyManagementException e) {
Log.e(TAG, "Failed to initialize trusting SSL context");
}
}

TRUSTING_CONTEXT = initializedContext;
}

private UnsafeConnectionBuilder() {
// no need to construct new instances
}

@NonNull
@Override
public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
Preconditions.checkNotNull(uri, "url must not be null");
Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()),
"scheme or uri must be http or https");
HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection();
conn.setConnectTimeout(CONNECTION_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setInstanceFollowRedirects(false);

if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) {
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory());
httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER);
}

return conn;
}
}
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface AuthConfiguration extends BaseAuthConfiguration {
scopes: string[];
redirectUrl: string;
additionalParameters?: { [name: string]: string };
dangerouslyAllowInsecureHttpRequests?: boolean;
}

export interface RevokeConfiguration {
Expand Down
Loading