Skip to content

Commit

Permalink
Merge pull request #3 from TransmitSecurity/feature/MOB-1140
Browse files Browse the repository at this point in the history
MOB-1140 - Update native SDK versions, implement biometric authentication
  • Loading branch information
shachartransmit authored Jul 17, 2024
2 parents d11f1fc + 7a658e0 commit f008c98
Show file tree
Hide file tree
Showing 16 changed files with 520 additions and 66 deletions.
115 changes: 95 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ To integrate this module, you'll need to configure an application.
npm install react-native-ts-authentication
```

#### iOS Setup
### iOS Setup
You might need to execute `pod install` in your project's `/ios` folder and set your minimum iOS target to 15.0 in your Podfile (e.g `platform :ios, 15.0`).

* Add project Capabilities as described [iOS quick start](https://developer.transmitsecurity.com/guides/webauthn/quick_start_sdk_ios/)
* Update YOUR Bundle ID and setup associated domains as described in the [iOS quick start](https://developer.transmitsecurity.com/guides/webauthn/quick_start_sdk_ios/)

#### Android Setup
### Android Setup

Add to `app/build.gradle` under repositories

Expand All @@ -58,16 +58,48 @@ repositories {
}
}
```
Note:
As for projects on Gradle 8+ and Kotlin 1.8+ build will fail if the JDK version between
compileKotlin and compileJava and jvmTarget are not aligned.

This won't be necessary anymore from React Native 0.73. More on this:
https://kotlinlang.org/docs/whatsnew18.html#obligatory-check-for-jvm-targets-of-related-kotlin-and-java-compile-tasks
#### Note:
As for projects on Gradle 8+ and Kotlin 1.8+ build will fail if the JDK version between compileKotlin and compileJava and jvmTarget are not aligned.
<br>
This won't be necessary anymore from React Native 0.73. More on this: https://kotlinlang.org/docs/whatsnew18.html#obligatory-check-for-jvm-targets-of-related-kotlin-and-java-compile-tasks

## Usage

#### Module Setup
### Module Setup

#### iOS
1. Open your project's `.xcworkspace` found under `YOUR_PROJECT_PATH/iOS` in Xcode.
2. Create a plist file named TransmitSecurity.plist in your Application with the following content. CLIENT_ID is configured in your Transmit server. Make sure the file is linked to your target.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>credentials</key>
<dict>
<!-- Use api.eu.transmitsecurity.io for EU, api.ca.transmitsecurity.io for CA -->
<key>baseUrl</key>
<string>https://api.transmitsecurity.io</string>
<key>clientId</key>
<string>CLIENT_ID</string>
</dict>
</dict>
</plist>
```
#### Android
1. Open your Android manifest XML file, usually located at `android/app/src/main`.
2. Update the strings.xml file in your Application with the following content. The CLIENT_ID should be replaced with your client ID

```xml
<resources>
<!-- Transmit Security Credentials -->
<string name="transmit_security_app_id">"default_application"</string>
<string name="transmit_security_client_id">"CLIENT_ID"</string>
<string name="transmit_security_base_url">https://api.transmitsecurity.io</string>
</resources>
```

```js
import TSAuthenticationSDKModule from 'react-native-ts-authentication';

Expand All @@ -77,18 +109,18 @@ componentDidMount(): void {
}

private onAppReady = async (): Promise<void> => {
/* Initialize the module with parameters:
1. ClientID obtained from the application settings in the Transmit portal
2. BaseURL can be "https://api.transmitsecurity.io" | eu = "api.eu.transmitsecurity.io" | ca = "api.ca.transmitsecurity.io"
TSAuthenticationSDKModule.initializeSDK();

/*
Instead of using Plist and strings.xml, you can initialize the module with parameters:
1. ClientID obtained from the application settings in the Transmit portal
2. Custom Domain - Can be null (or undefined if not using BaseURL)
3. BaseURL - Can be null or undefined. "https://api.transmitsecurity.io" | eu = "api.eu.transmitsecurity.io" | ca = "api.ca.transmitsecurity.io"
TSAuthenticationSDKModule.initialize(
"YOUR_CLIENT_ID"
);
*/
const baseURL = "https://api.transmitsecurity.io";

TSAuthenticationSDKModule.initialize(
"YOUR_CLIENT_ID",
"YOUR_DOMAIN",
`${baseURL}/cis/v1`
);
}
```

Expand Down Expand Up @@ -128,6 +160,49 @@ onStartSignTransactionProcess = async (): Promise<void> => {
}
```

### Native Biometrics
• For iOS, ensure that you add the necessary permissions to use FaceID in your app's Info.plist file.<br>
• For Android, add the following strings to your app's strings.xml file:

```xml
<resources>
<string name="BiometricPromptTitle">Authenticate with Biometrics</string>
<string name="BiometricPromptSubtitle">Use your device biometrics to authenticate.</string>
<string name="BiometricPromptCancel">Cancel</string>
</resources>
```

#### Register Native Biometrics
```js
onRegisterNativeBiometics = async (username: string): Promise<void> => {
try {
const response = await TSAuthenticationSDKModule.registerNativeBiometrics(username);
// use the response.result string to complete biometrics registration in your backend.
} catch (error) {
console.error(`Error signing a transaction: ${error}`);
}
}
```

#### Authenticate Biometrics
```js
authenticateWithNativeBiometrics = async (username: string): Promise<void> => {
try {
const challenge = this.randomString();
const response = await TSAuthenticationSDKModule.authenticateNativeBiometrics(username, challenge);
// use the response.result string to complete biometrics authentication in your backend.
} catch (error) {
console.error(`Error signing a transaction: ${error}`);
}
}

private randomString = (): string => {
return (Math.random() + 1).toString(36).substring(7);
}
```

### Information about the device

#### Get Device Info
```js
onGetDeviceInfo = async (): Promise<void> => {
Expand Down
8 changes: 8 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## 0.1.4 July 2024
### Content
#### Enhancements
1. Upgraded native SDKs to iOS `1.1.3` and Android `1.0.19`.
2. Added initializeSDK API to load configuration from `TransmitSecurity.plist` on iOS, and `strings.xml` on Android.
3. Added biometrics authentication API.
4. `uses-sdk:minSdkVersion` Should be equal or greater then `23` to support native biometrics.
5. Added support for custom domain in SDK initialize API.
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation("com.ts.sdk:authentication:1.0.+")
implementation("com.ts.sdk:authentication:1.0.19+")
implementation "com.facebook.react:react-native:+"
}

2 changes: 1 addition & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
TsAuthentication_kotlinVersion=1.7.0
TsAuthentication_minSdkVersion=21
TsAuthentication_minSdkVersion=23
TsAuthentication_targetSdkVersion=31
TsAuthentication_compileSdkVersion=31
TsAuthentication_ndkversion=21.4.7075529
131 changes: 126 additions & 5 deletions android/src/main/java/com/tsauthentication/TsAuthenticationModule.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package com.tsauthentication;

import android.app.Activity;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
Expand All @@ -15,9 +21,15 @@
import com.transmit.authentication.TSAuthCallback;
import com.transmit.authentication.TSAuthentication;
import com.transmit.authentication.TSWebAuthnRegistrationError;
import com.transmit.authentication.biometrics.BiometricPromptTexts;
import com.transmit.authentication.biometrics.TSBiometricsAuthError;
import com.transmit.authentication.biometrics.TSBiometricsAuthResult;
import com.transmit.authentication.biometrics.TSBiometricsRegistrationError;
import com.transmit.authentication.biometrics.TSBiometricsRegistrationResult;
import com.transmit.authentication.network.completereg.DeviceInfo;

import java.util.HashMap;
import java.util.Map;

@ReactModule(name = TsAuthenticationModule.NAME)
public class TsAuthenticationModule extends ReactContextBaseJavaModule {
Expand All @@ -39,10 +51,22 @@ public String getName() {
@NonNull public void initialize(String clientId, String domain, String baseUrl, Promise promise) {

if(reactContext.getCurrentActivity() != null) {
TSAuthentication.initialize(
reactContext,
clientId
);

if (domain.length() > 0) {
TSAuthentication.initialize(
reactContext,
clientId,
baseUrl,
domain
);
} else {
TSAuthentication.initialize(
reactContext,
clientId,
baseUrl,
null
);
}
promise.resolve(true);
}
}
Expand Down Expand Up @@ -81,7 +105,7 @@ public void success(RegistrationResult registrationResult) {
}
@Override
public void error(TSWebAuthnRegistrationError tsWebAuthnRegistrationError) {
promise.reject("result", tsWebAuthnRegistrationError.getEM());
promise.reject("result", tsWebAuthnRegistrationError.getErrorMessage());
}
}
);
Expand Down Expand Up @@ -134,6 +158,103 @@ public void error(TSWebAuthnAuthenticationError tsWebAuthnAuthenticationError) {
}
}

// Native Biometrics

@ReactMethod
@NonNull public void registerNativeBiometrics(String username, Promise promise) {
if(reactContext.getCurrentActivity() != null) {
TSAuthentication.registerNativeBiometrics(
reactContext.getCurrentActivity(),
username,
new TSAuthCallback<TSBiometricsRegistrationResult, TSBiometricsRegistrationError>() {
@Override
public void success(TSBiometricsRegistrationResult tsBiometricsRegistrationResult) {
WritableMap map = new WritableNativeMap();
map.putString("publicKeyId", tsBiometricsRegistrationResult.keyId());
map.putString("publicKey", tsBiometricsRegistrationResult.publicKey());
map.putString("os", "Android");
promise.resolve(map);
}

@Override
public void error(TSBiometricsRegistrationError tsBiometricsRegistrationError) {
promise.reject("result", tsBiometricsRegistrationError.toString());
}
}
);
}
}

@ReactMethod
@NonNull public void authenticateNativeBiometrics(String username, String challenge, Promise promise) {
if(reactContext.getCurrentActivity() != null) {

AppCompatActivity appCompatActivity = getAppCompatActivity();
if (appCompatActivity == null) {
promise.reject("result", "current activity is not an instance of AppCompatActivity");
return;
}

Map<String, String> biometricsString = getBiometricsStrings();
BiometricPromptTexts promptTexts = new BiometricPromptTexts(
biometricsString.get("titleTxt"),
biometricsString.get("subtitleTxt"),
biometricsString.get("cancelTxt")
);

TSAuthentication.authenticateNativeBiometrics(
appCompatActivity,
username,
challenge,
promptTexts,
new TSAuthCallback<TSBiometricsAuthResult, TSBiometricsAuthError>() {
@Override
public void success(TSBiometricsAuthResult tsBiometricsAuthResult) {
WritableMap map = new WritableNativeMap();
map.putString("publicKeyId", tsBiometricsAuthResult.keyId());
map.putString("signature", tsBiometricsAuthResult.signature());
promise.resolve(map);
}

@Override
public void error(TSBiometricsAuthError tsBiometricsAuthError) {
promise.reject("result", tsBiometricsAuthError.toString());
}
}
);
}
}

@Nullable
private AppCompatActivity getAppCompatActivity() {
Activity activity = reactContext.getCurrentActivity();
if (activity instanceof AppCompatActivity) {
return (AppCompatActivity) activity;
} else {
return null;
}
}

private Map<String, String> getBiometricsStrings() {
Context context = reactContext;

String titleTxt = getStringResourceByName(context, "BiometricPromptTitle", "Authenticate with Biometrics");
String subtitleTxt = getStringResourceByName(context, "BiometricPromptSubtitle", "Use your device biometrics to authenticate.");
String cancelTxt = getStringResourceByName(context, "BiometricPromptCancel", "Cancel");

Map<String, String> biometricsStrings = new HashMap<>();
biometricsStrings.put("titleTxt", titleTxt);
biometricsStrings.put("subtitleTxt", subtitleTxt);
biometricsStrings.put("cancelTxt", cancelTxt);

return biometricsStrings;
}

private String getStringResourceByName(Context context, String resourceName, String defaultValue) {
int resId = context.getResources().getIdentifier(resourceName, "string", context.getPackageName());
return resId != 0 ? context.getString(resId) : defaultValue;
}

@ReactMethod
@NonNull public void getDeviceInfo(Promise promise) {
if(reactContext.getCurrentActivity() != null) {
Expand Down
3 changes: 3 additions & 0 deletions example/android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
\"include\": \"https://mobile.idsec-dev.com/.well-known/assetlinks.json\"
}]
</string>
<string name="BiometricPromptTitle">Authenticate with Biometrics</string>
<string name="BiometricPromptSubtitle">Use your device biometrics to authenticate.</string>
<string name="BiometricPromptCancel">Cancel</string>
</resources>
2 changes: 1 addition & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
buildscript {
ext {
buildToolsVersion = "34.0.0"
minSdkVersion = 21
minSdkVersion = 23
compileSdkVersion = 34
targetSdkVersion = 34
kotlin_version = "1.9.20"
Expand Down
Loading

0 comments on commit f008c98

Please sign in to comment.