Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ captures
*.iml
.idea

# VSCode files
.vscode

# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,17 @@ These parameters are overrideable in every platform
| customHandlerClass | | | Provide a class name implementing `com.getcapacitor.community.genericoauth2.handler.OAuth2CustomHandler` | |
| handleResultOnNewIntent | `false` | | Alternative to handle the activity result. The `onNewIntent` method is only call if the App was killed while logging in. | |
| handleResultOnActivityResult | `true` | | | |
| rawPkcs | | | Provide raw PKCS data from a .p12 or .pfx file as a base64 encoded string for mTLS authentication. | |
| pkcsPassword | | | Provide an optional password for the PKCS data if it is password secured. | |

**Platform iOS**

| parameter | default | required | description | since |
|--------------------|---------|----------|------------------------------------------------------------------------------------------------|-------|
| customHandlerClass | | | Provide a class name implementing `CapacitorCommunityGenericOauth2.OAuth2CustomHandler` | |
| siwaUseScope | | | SiWA default scope is `name email` if you want to use the configured one set this param `true` | 2.1.0 |
| parameter | default | required | description | since |
|--------------------|---------|----------|----------------------------------------------------------------------------------------------------|-------|
| customHandlerClass | | | Provide a class name implementing `CapacitorCommunityGenericOauth2.OAuth2CustomHandler` | |
| siwaUseScope | | | SiWA default scope is `name email` if you want to use the configured one set this param `true` | 2.1.0 |
| rawPkcs | | | Provide raw PKCS data from a .p12 or .pfx file as a base64 encoded string for mTLS authentication. | |
| pkcsPassword | | | Provide an optional password for the PKCS data if it is password secured. | |

#### logout()

Expand Down Expand Up @@ -254,6 +258,7 @@ See [Issue #97](https://github.com/capacitor-community/generic-oauth2/issues/97)
- ERR_ANDROID_NO_BROWSER ... No suitable browser could be found! (Android)
- ERR_ANDROID_RESULT_NULL ... The auth result is null. The intent in the ActivityResult is null. This might be a valid
state but make sure you configured Android part correctly! See [Platform Android](#platform-android)
- ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED ... Importing the provided pkcs data was not successful. Most likely the string you provided is in the wrong format. A base64 encoded string of the raw pkcs data file is expected. Also check if the file is protected by a password and if so, if you provided the correct password accordingly
- ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (web, android, ios)

#### refreshToken()
Expand All @@ -263,6 +268,7 @@ See [Issue #97](https://github.com/capacitor-community/generic-oauth2/issues/97)
authenticate it is optional. (android, ios)
- ERR_PARAM_NO_REFRESH_TOKEN ... The refresh token is missing. (android, ios)
- ERR_NO_ACCESS_TOKEN ... No access_token found. (web, android)
- ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED ... Importing the provided pkcs data was not successful. Most likely the string you provided is in the wrong format. A base64 encoded string of the raw pkcs data file is expected. Also check if the file is protected by a password and if so, if you provided the correct password accordingly
- ERR_GENERAL ... A unspecific error. Check the logs to see want exactly happened. (android, ios)

## Platform: Web/PWA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import org.json.JSONException;

import com.getcapacitor.community.genericoauth2.MTLSHelper;
@CapacitorPlugin(name = "GenericOAuth2")
public class GenericOAuth2Plugin extends Plugin {

Expand Down Expand Up @@ -62,6 +62,10 @@ public class GenericOAuth2Plugin extends Plugin {
private static final String PARAM_LOGOUT_URL = "logoutUrl";
private static final String PARAM_ID_TOKEN = "id_token";

// mTLS params
private static final String PARAM_RAW_PKCS = "rawPkcs";
private static final String PARAM_PKCS_PASSWORD = "pkcsPassword";

private static final String USER_CANCELLED = "USER_CANCELLED";

private static final String ERR_PARAM_NO_APP_ID = "ERR_PARAM_NO_APP_ID";
Expand All @@ -84,6 +88,8 @@ public class GenericOAuth2Plugin extends Plugin {
private static final String ERR_STATES_NOT_MATCH = "ERR_STATES_NOT_MATCH";
private static final String ERR_NO_AUTHORIZATION_CODE = "ERR_NO_AUTHORIZATION_CODE";

private static final String ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED = "ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED";

private OAuth2Options oauth2Options;
private AuthorizationService authService;
private AuthState authState;
Expand Down Expand Up @@ -111,6 +117,14 @@ public void refreshToken(final PluginCall call) {
return;
}

if (oAuth2RefreshTokenOptions.getRawPkcs() != null && !oAuth2RefreshTokenOptions.getRawPkcs().isEmpty()) {
try {
MTLSHelper.configureMTLS(getContext(), oAuth2RefreshTokenOptions.getRawPkcs(), oAuth2RefreshTokenOptions.getPkcsPassword());
} catch (Exception e) {
call.reject(ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED, e);
}
}

this.authService = new AuthorizationService(getContext());

AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration(
Expand Down Expand Up @@ -156,6 +170,15 @@ public void authenticate(final PluginCall call) {
this.callbackId = call.getCallbackId();
disposeAuthService();
oauth2Options = buildAuthenticateOptions(call.getData());

if (oauth2Options.getRawPkcs() != null && !oauth2Options.getRawPkcs().isEmpty()) {
try {
MTLSHelper.configureMTLS(getContext(), oauth2Options.getRawPkcs(), oauth2Options.getPkcsPassword());
} catch (Exception e) {
call.reject(ERR_MTLS_CLIENT_CERTIFICATE_IMPORT_FAILED, e);
}
}

if (oauth2Options.getCustomHandlerClass() != null) {
if (oauth2Options.isLogsEnabled()) {
Log.i(getLogTag(), "Entering custom handler: " + oauth2Options.getCustomHandlerClass().getClass().getName());
Expand Down Expand Up @@ -511,6 +534,8 @@ OAuth2Options buildAuthenticateOptions(JSObject callData) {
if (o.isPkceEnabled()) {
o.setPkceCodeVerifier(ConfigUtils.getRandomString(64));
}
o.setRawPkcs(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_RAW_PKCS)));
o.setPkcsPassword(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_PKCS_PASSWORD)));

o.setScope(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_SCOPE)));
o.setState(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_STATE)));
Expand Down Expand Up @@ -556,6 +581,10 @@ OAuth2RefreshTokenOptions buildRefreshTokenOptions(JSObject callData) {
);
o.setScope(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_SCOPE)));
o.setRefreshToken(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_REFRESH_TOKEN)));

// mTLS
o.setRawPkcs(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_RAW_PKCS)));
o.setPkcsPassword(ConfigUtils.trimToNull(ConfigUtils.getOverwrittenAndroidParam(String.class, callData, PARAM_PKCS_PASSWORD)));
return o;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.getcapacitor.community.genericoauth2;

import android.content.Context;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.security.KeyStore;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

public class MTLSHelper {
private static final String TAG = "MTLSHelper";
private static SSLContext sslContext;

public static void configureMTLS(Context context, String rawPkcs, String pkcsPassword) {
if (rawPkcs == null || rawPkcs.isEmpty()) {
Log.d(TAG, "No certificate data provided, skipping mTLS configuration");
return;
}

try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
byte[] pkcsData = android.util.Base64.decode(rawPkcs, android.util.Base64.DEFAULT);
keyStore.load(new ByteArrayInputStream(pkcsData), pkcsPassword.toCharArray());

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, pkcsPassword.toCharArray());

sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);

HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

Log.d(TAG, "mTLS client certificate configured successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to configure mTLS", e);
throw new RuntimeException("mTLS configuration failed", e);
}
}

public static SSLContext getSSLContext() {
return sslContext;
}

public static void resetSSLContext() {
try {
SSLContext defaultContext = SSLContext.getInstance("TLS");
defaultContext.init(null, null, null);
HttpsURLConnection.setDefaultSSLSocketFactory(defaultContext.getSocketFactory());
sslContext = null;
Log.d(TAG, "SSL context reset to default");
} catch (Exception e) {
Log.e(TAG, "Failed to reset SSL context", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public class OAuth2Options {
private boolean handleResultOnNewIntent;
private boolean handleResultOnActivityResult = true;

private String rawPkcs;
private String pkcsPassword;

private String display;
private String loginHint;
private String prompt;
Expand Down Expand Up @@ -216,4 +219,20 @@ public void addAdditionalResourceHeader(String key, String value) {
public String getLogoutUrl() {
return logoutUrl;
}

public String getRawPkcs() {
return rawPkcs;
}

public void setRawPkcs(String rawPkcs) {
this.rawPkcs = rawPkcs;
}

public String getPkcsPassword() {
return pkcsPassword;
}

public void setPkcsPassword(String pkcsPassword) {
this.pkcsPassword = pkcsPassword;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public class OAuth2RefreshTokenOptions {
private String accessTokenEndpoint;
private String refreshToken;
private String scope;
private String rawPkcs;
private String pkcsPassword;

public String getAppId() {
return appId;
Expand Down Expand Up @@ -38,4 +40,20 @@ public String getScope() {
public void setScope(String scope) {
this.scope = scope;
}

public String getRawPkcs() {
return rawPkcs;
}

public void setRawPkcs(String rawPkcs) {
this.rawPkcs = rawPkcs;
}

public String getPkcsPassword() {
return pkcsPassword;
}

public void setPkcsPassword(String pkcsPassword) {
this.pkcsPassword = pkcsPassword;
}
}
6 changes: 6 additions & 0 deletions ios/Plugin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */; };
20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */; };
22BF03F12E44D2D30092F0FA /* URLSessionMTLSDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BF03F02E44D2D30092F0FA /* URLSessionMTLSDelegate.swift */; };
22BF03F22E44D2D30092F0FA /* URLSessionMTLSDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22BF03F02E44D2D30092F0FA /* URLSessionMTLSDelegate.swift */; };
451C6E972BE3BF4400D9577D /* OAuth2CustomHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451C6E962BE3BF4400D9577D /* OAuth2CustomHandler.swift */; };
451C6E992BE3BF7200D9577D /* OAuth2SafariDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451C6E982BE3BF7200D9577D /* OAuth2SafariDelegate.swift */; };
451C6E9B2BE3BF9F00D9577D /* GenericOAuth2Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 451C6E9A2BE3BF9F00D9577D /* GenericOAuth2Plugin.swift */; };
Expand All @@ -31,6 +33,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
22BF03F02E44D2D30092F0FA /* URLSessionMTLSDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionMTLSDelegate.swift; sourceTree = "<group>"; };
3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; };
451C6E962BE3BF4400D9577D /* OAuth2CustomHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2CustomHandler.swift; sourceTree = "<group>"; };
451C6E982BE3BF7200D9577D /* OAuth2SafariDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2SafariDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -96,6 +99,7 @@
50ADFF8A201F53D600D50D53 /* Plugin */ = {
isa = PBXGroup;
children = (
22BF03F02E44D2D30092F0FA /* URLSessionMTLSDelegate.swift */,
451C6E982BE3BF7200D9577D /* OAuth2SafariDelegate.swift */,
451C6E962BE3BF4400D9577D /* OAuth2CustomHandler.swift */,
50E1A94720377CB70090CE1A /* GenericOAuth2Plugin.swift */,
Expand Down Expand Up @@ -315,6 +319,7 @@
buildActionMask = 2147483647;
files = (
451C6E972BE3BF4400D9577D /* OAuth2CustomHandler.swift in Sources */,
22BF03F12E44D2D30092F0FA /* URLSessionMTLSDelegate.swift in Sources */,
50E1A94820377CB70090CE1A /* GenericOAuth2Plugin.swift in Sources */,
50ADFFA82020EE4F00D50D53 /* GenericOAuth2Plugin.m in Sources */,
451C6E992BE3BF7200D9577D /* OAuth2SafariDelegate.swift in Sources */,
Expand All @@ -327,6 +332,7 @@
buildActionMask = 2147483647;
files = (
50ADFF97201F53D600D50D53 /* GenericOAuth2Tests.swift in Sources */,
22BF03F22E44D2D30092F0FA /* URLSessionMTLSDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Loading