Skip to content

Commit

Permalink
MOB-743 #comment updated documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
shachartransmit committed Dec 22, 2023
1 parent e3abfd7 commit 38f4cb0
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 66 deletions.
139 changes: 132 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,143 @@
# react-native-ts-authentication
# React Native - Transmit Security Authentication SDK

React Native module for Transmit Security Authentication SDK
Add strong authentication with Passkeys to your native iOS and Android applications, while providing a native experience. This describes how to use the React Native module to register credentials and use them to authenticate your users.

## About Authentication SDK

This SDK provides a unified solution for implementing both Apple's public-private key authentication for passkeys on iOS and Google's Credential Manager API for passkeys on Android. It enables the integration of FIDO2-based biometric authentication seamlessly into your mobile applications, offering users a native experience instead of a browser-based one. With passkeys, credentials are securely stored by the device, leveraging iCloud Keychain on iOS and Google Password Manager on Android. These credentials are associated with your domain, facilitating secure sharing between your mobile app and website if applicable.

Using this module, you can easily integrate our Authentication SDK into your React Native app for seamless and secure user identity authentication.<br>
[Learn more about how you can boost your security with Transmit Security Authentication.](https://transmitsecurity.com/platform/full-stack-authentication)

## Understand the flow
We recommended that you read more about the verification flow required steps in our [iOS documentation](https://developer.transmitsecurity.com/guides/webauthn/quick_start_sdk_ios/) and [Android documentation](https://developer.transmitsecurity.com/guides/webauthn/quick_start_sdk_android/#Command-line)

## Configure your app
To integrate this module, you'll need to configure an application.

1. From the [Applications](https://portal.transmitsecurity.io/applications) page, [create a new application](https://developer.transmitsecurity.com/guides/user/create_new_application/) or use an existing one.
2. From the application settings:
* For Client type , select native
* For Redirect URI , enter your website URL. This is a mandatory field, but it isn't used for this flow.
* Obtain your client ID and secret for API calls, which are autogenerated upon app creation.
* Enable public sign-up if you manage users using an external system (e.g., external identity provider) or if you want to quickly test WebAuthn registration and authentication without first logging in using a different authentication method.
3. Refer to our iOS and Android documentation mentioned above to configure an auth method and associate your domain for Apple and Google.

## Example project setup
#### Note: Configuring Google's assetlinks.json and Apple's apple-app-site-association according to the guidelines in our native SDKs documentation and the user-guides provided by Apple and Google can be a challenging task. However, it is crucial to complete this step accurately for both utilizing the example app and configuring your own application. Instead of attempting to configure this example directly, you are welcome to use it just as a code-reference to ensure proper implementation.

1. In your project, navigate to `example/src/config.ts` and configure the clientId, domain, secret and baseUrl using the configuration obtained from the Transmit portal.
2. Ensure you have all the necessary dependencies by running `yarn` in both the module's root folder and the example root folder.
3. Run the example app on a real device using Xcode or Android Studio. Alternatively, execute `yarn example ios` or `yarn example android`.
<br><br>
> **Important Security Note: Never store your `secret` in a front-end application.**
>
> The example app utilizes a mock server to manage communication with the authentication platform. This mock server employs the `secret` you have specified in `example/src/config.ts` exclusively for demonstration purposes. It is paramount that you safeguard your `secret` in a secure and confidential location.
## Installation

```sh
npm install react-native-ts-authentication
```
ios setup

platform :ios, 15.0
#### 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

Add to `app/build.gradle` under repositories

```gradle
repositories {
google()
maven {
url('https://transmit.jfrog.io/artifactory/transmit-security-gradle-release-local/')
}
}
```
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

## Usage

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

componentDidMount(): void {
// Setup the module as soon your component is ready
this.onAppReady().catch(e => void e);
}

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"
*/
const baseURL = "https://api.transmitsecurity.io";

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

#### First time authentication (Register a user)
```js
onStartRegistrationProcess = async (): Promise<void> => {
try {
const response = await TSAuthenticationSDKModule.register(username, displayName);
// use the response.result string to complete a successful registration in your backend.
} catch (error) {
console.error(`Error authentication the user: ${error}`);
}
}
```

#### Start the authentication process
```js
onStartAuthenticationProcess = async (): Promise<void> => {
try {
const response = await TSAuthenticationSDKModule.authenticate(username);
// use the response.result string to complete a successful authentication in your backend.
} catch (error) {
console.error(`Error authentication the user: ${error}`);
}
}
```

#### Sign a transaction
```js
onStartSignTransactionProcess = async (): Promise<void> => {
try {
const response = await TSAuthenticationSDKModule.signTransaction(username);
// use the response.result string to complete a signing a transaction in your backend.
} catch (error) {
console.error(`Error authentication the user: ${error}`);
}
}
```

## Important Notes
1. Please take note that the example application uses a client-side mock server. In a production environment, a real server is required. Additionally, it is crucial to emphasize that storing the client secret in your front-end application is strictly discouraged for security reasons.

## Support
[Email us for support]([email protected])

## Author

add the capabilities
https://developer.transmitsecurity.com/guides/webauthn/quick_start_sdk_ios/
Transmit Security, https://github.com/TransmitSecurity

## License

update bundle id and setup associated domains iOS format example: webcredentials:shopcart.userid-stg.io (instruct to follow the tutorial for each platform)
This project is licensed under the MIT license. See the LICENSE file for more info.
139 changes: 96 additions & 43 deletions android/src/main/java/com/tsauthentication/TsAuthenticationModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.module.annotations.ReactModule;
Expand Down Expand Up @@ -35,11 +36,17 @@ public String getName() {

@ReactMethod
@NonNull public void initialize(String clientId, String domain, String baseUrl, Promise promise) {
TSAuthentication.init(getReactApplicationContext(),
baseUrl,
clientId
);
promise.resolve(true);
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
TSAuthentication.init(getReactApplicationContext(),
baseUrl,
clientId
);
promise.resolve(true);
}
});
}

// Registration
Expand All @@ -49,62 +56,108 @@ public String getName() {
String username,
String displayName,
Promise promise) {
TSAuthentication.isPlatformAuthenticatorSupported(
getReactApplicationContext(),
new TSAuthCallback<Boolean>() {
@Override
public void success(Boolean aBoolean) {
continueRegistration(username, displayName, promise);
}

UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(new Error("Unsupported platform"));
public void run() {
TSAuthentication.isPlatformAuthenticatorSupported(
getReactApplicationContext(),
new TSAuthCallback<Boolean>() {
@Override
public void success(Boolean aBoolean) {
continueRegistration(username, displayName, promise);
}

@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(new Error("Unsupported platform"));
}
}
);
}
}
);
});
}
private void continueRegistration(String username, String displayName, Promise promise) {
TSAuthentication.register(
getReactApplicationContext(),
username,
displayName,
new TSAuthCallback<RegistrationResult>() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void success(RegistrationResult registrationResult) {
WritableMap map = new WritableNativeMap();
map.putString(registrationResult.result(), NAME);
promise.resolve(map);
}
public void run() {
TSAuthentication.register(
getReactApplicationContext(),
username,
displayName,
new TSAuthCallback<RegistrationResult>() {
@Override
public void success(RegistrationResult registrationResult) {
WritableMap map = new WritableNativeMap();
map.putString(registrationResult.result(), NAME);
promise.resolve(map);
}

@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(NAME, authenticationError.toString());
@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(NAME, authenticationError.toString());
}
}
);
}
}
);
});
}

// Authentication
@ReactMethod
@NonNull public void authenticate(String username, Promise promise) {
TSAuthentication.authenticate(
getReactApplicationContext(),
username,
new TSAuthCallback<AuthenticationResult>() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void success(AuthenticationResult authenticationResult) {
WritableMap map = new WritableNativeMap();
map.putString(authenticationResult.result(), NAME);
promise.resolve(map);
public void run() {
TSAuthentication.authenticate(
getReactApplicationContext(),
username,
new TSAuthCallback<AuthenticationResult>() {
@Override
public void success(AuthenticationResult authenticationResult) {
WritableMap map = new WritableNativeMap();
map.putString(authenticationResult.result(), NAME);
promise.resolve(map);
}

@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(NAME, authenticationError.toString());
}
}
);
}
});
}

@ReactMethod
@NonNull public void signTransaction(String username, Promise promise) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(NAME, authenticationError.toString());
public void run() {
TSAuthentication.signTransaction(
getReactApplicationContext(),
username,
new TSAuthCallback<AuthenticationResult>() {
@Override
public void success(AuthenticationResult authenticationResult) {
WritableMap map = new WritableNativeMap();
map.putString(authenticationResult.result(), NAME);
promise.resolve(map);
}

@Override
public void error(@NonNull AuthenticationError authenticationError) {
promise.reject(NAME, authenticationError.toString());
}
}
);
}
}
);
});
}
}

2 changes: 1 addition & 1 deletion example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ android {

namespace "com.tsauthenticationexample"
defaultConfig {
applicationId "com.transmitsecurity.authsdk-rn-example"
applicationId "com.transmitsecurity.authsdk_rn_example"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
Expand Down
Binary file added example/android/app/debug/app-debug.aab
Binary file not shown.
3 changes: 2 additions & 1 deletion example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
minSdkVersion = 21
compileSdkVersion = 34
targetSdkVersion = 34

kotlin_version = "1.9.20"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
Expand All @@ -17,6 +17,7 @@ buildscript {
dependencies {
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Expand Down
2 changes: 1 addition & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { SafeAreaView } from 'react-native';
import TSAuthenticationSDKModule, { TSAuthenticationSDK } from 'react-native-ts-authentication';
import TSAuthenticationSDKModule from 'react-native-ts-authentication';
import HomeScreen from './home';
import config from './config';
import localUserStore from './utils/local-user-store';
Expand Down
6 changes: 3 additions & 3 deletions example/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export default {
clientId: "5nhc16n9f165t4gp0todzd37qzfr9oxq",
domain: "shopcart.userid-stg.io",
secret: "aa14b16a-87fb-43aa-8372-df130b25d1ad",
clientId: "CLIENT_ID_FROM_PORTAL",
domain: "ASSOCIATED_DOMAIN",
secret: "SECRET_FROM_PORTAL", // Important! This is just for demo purposes. Never store your secret in the client side.
baseUrl: "https://api.transmitsecurity.io", // eu = "api.eu.transmitsecurity.io" ca = "api.ca.transmitsecurity.io"
}
2 changes: 1 addition & 1 deletion example/src/logged-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import React, { type ReactElement } from 'react';
import { View, StyleSheet, Text, Button, TextInput } from 'react-native';
import { View, StyleSheet, Text, Button } from 'react-native';

export type Props = {
username: string;
Expand Down
14 changes: 14 additions & 0 deletions src/scripts/generate_apk_keyhash.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

# Edit the following variables to match your keystore and app:
KEY_STORE_ALIAS="YOUR_KEY_STORE_ALIAS"
PATH_KEY_STORE_ALIAS_FOLDER="PATH_TO_KEY_STORE_FOLDER"
KEY_STORE_FILE_NAME_IN_FOLDER="YOUR_KEY_STORE_FILE_NAME_IN_FOLDER"

cd $PATH_KEY_STORE_ALIAS_FOLDER
# Export the signing certificate in DER format, hash, base64 encode, trim '=' and url encode

echo "Generating key hash for Android app..."
keytool -exportcert -alias $KEY_STORE_ALIAS -keystore $KEY_STORE_FILE_NAME_IN_FOLDER | openssl sha256 -binary | openssl base64 | sed 's/=//g'| sed s/\\+/-/g | sed s/\\//_/g | sed -E s/=+\$//

echo "All done."
Loading

0 comments on commit 38f4cb0

Please sign in to comment.