Passwordless Mobile Authentication and SIM Swap Detection with React Native

In this tutorial, you'll learn how to add passwordless authentication to a React Native application by using SubscriberCheck. This check will verify the phone number associated with the SIM card on a mobile device and check if that SIM card has changed recently to detect potential SIM swap attacks.
Timothy OgbemudiaDeveloper Experience Engineer
Last updated: 20 September 2022
tutorial cover image

Before we begin, let's discuss why you want to secure your applications with the tru.ID SubscriberCheck API.

The tru.ID SubscriberCheck API

The tru.ID SubscriberCheck API confirms the ownership of a mobile phone number by verifying the possession of an active SIM card with the same number. It also exposes a flag that indicates if the SIM card associated with the mobile phone number has changed within the last seven days. This check provides the security required for passwordless login with the addition of attempted fraud detection.

All your user needs to do is provide a phone number, and the API will verify that number is associated with their device. It can be used as a primary user verification mechanism or as an added layer of security against SIM swapping/theft within existing 2FA workflows. In this tutorial, we'll use SubscriberCheck as a standalone authentication mechanism.

The SubscriberCheck workflow is as follows:

  1. Get the user's phone number on the mobile device
  2. Send the phone number to the application server
  3. Create a SubscriberCheck using the SubscriberCheck API
  4. Return the SubscriberCheck URL to the mobile device
  5. Request the SubscriberCheck URL on the mobile device over mobile data
  6. Mobile device requests the SubscriberCheck result via the application server
  7. Application server completes the phone check a PATCH request to tru.ID's API
  8. Application server returns the SubscriberCheck result, including SIM changed status, to the mobile device

In this tutorial, we'll use a ready-made server and focus on the steps involving the React Native application.

Before you begin

Before you begin, there are a few requirements that have to be met:

  • An Android or Apple device with a SIM card and mobile data connection
  • For iOS: XCode >12
  • For Android:
  • For metro bundler, Node.js version > 10

Getting Started

A tru.ID Account is needed to make the SubscriberCheck API requests, so make sure you've created one.You're also going to need some Project credentials from tru.ID to make API calls. So sign up for a tru.ID account, which comes with some free credit. We've built a CLI for you to manage your tru.ID account, projects, and credentials within your Terminal. To install the tru.ID CLI run the following command:
npm install -g @tru_id/cli
Run tru login <YOUR_IDENTITY_PROVIDER> (this is one of google, github, or microsoft) using the Identity Provider you used when signing up. This command will open a new browser window and ask you to confirm your login. A successful login will show something similar to the below:
Success. Tokens were written to /Users/user/.config/@tru_id/cli/config.json. You can now close the browser
Note: The config.json file contains some information you won't need to modify. This config file includes your Workspace Data Residency (EU, IN, US), your Workspace ID, and token information such as your scope.Create a new tru.ID project within the root directory with the following command:
tru projects:create rn-auth --project-dir .
Your project will need a backend server for your mobile application to communicate with. We've created the dev-server for you to get started with this tutorial as quickly as possible. The development server is written in Javascript and is not production ready, so it should only be used to understand the flow of running a Check.In your Terminal, navigate to the rn-auth directory and run the following command to clone the dev-server:
git clone git@github.com:tru-ID/dev-server.git
In the dev-server directory, run the following command to create a.env copy of .env.example:
cp .env.example .env
Open this new .env file, update the values of TRU_ID_CLIENT_ID andTRU_ID_CLIENT_SECRET with your client_id and client_secret in yourtru.json file.
You'll need to expose your dev-server to the Internet for your mobile application to access your backend server. For this tutorial, we're using ngrok, which we've included in the dev-server functionality. So to start with, uncomment the following and populate them with your ngrok credentials:
NGROK_ENABLED=true
#NGROK_SUBDOMAIN=a-subdomain # Uncommenting this is optional. It is only available if you have a paid ngrok account.
NGROK_AUTHTOKEN=<YOUR_NGROK_AUTHTOKEN> # This is found in the ngrok dashboard: https://dashboard.ngrok.com/get-started/your-authtoken
NGROK_REGION=eu # This is where your ngrok URL will be hosted. If you're using tru.ID's `eu` data residency; leave it as is. Otherwise, you could specify `in` or `us`.
Run the development server; first, you'll need to install third-party dependencies. In the dev-server directory, run the following two commands:
npm install # Installs all third-party dependencies in package.json
npm run dev # Starts the server
Open up the URL shown in the terminal, which will be in the format in your desktop web browser to check that it is accessible.With the development server setup, we can move on to building the application.

To follow along with this tutorial, head over to the Passwordless Authentication with React Native repo and clone the starter-files branch via:

git clone -b starter-files https://github.com/tru-ID/passwordless-auth-react-native.git

To install dependencies, open a new terminal, cd passwordless-auth-react-native, and run:

npm install

Once you've finished that step, start the Metro server:

npm start

Then, to run on Android, ensure your Android device is connected via USB and run:

npm run android

To run on your iOS device, ensure it's connected via USB, open ios/simcardauthrn.xcworkspace in XCode, and run the project.

Your app should look like this:

React Native Starter App

Get the User's Phone Number on the Mobile Device

Let's start by adding the UI and state management required for the user to input (TextInput) and submit their phone number (TouchableOpacity). We'll also add a UI and state to check if any work is in progress via a loading variable or an error has occurred via error.

const App = () => {
const [phoneNumber, setPhoneNumber] = React.useState('')
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState(null)
React.useEffect(() => {
if (error) {
showMessage({
message: error,
type: 'danger',
style: styles.toastContainer,
})
}
}, [error])
// we'll handle SubscriberCheck in the function below
const onPressHandler = () => {}
return (
<>
<StatusBar barStyle="light-content" />
<SafeAreaView style={styles.container}>
<Image style={styles.logo} source={require('./images/tru-logo.png')} />
<Text style={styles.heading}>Enter your phone number</Text>
<Text style={styles.paragraph}>and we'll handle the rest</Text>
<View style={styles.center}>
<TextInput
style={styles.textInput}
keyboardType="phone-pad"
placeholder="ex. (415) 555-0100"
placeholderTextColor="#d3d3d3"
onChangeText={(text) => setPhoneNumber(text)}
value={phoneNumber}
/>
{loading ? (
<ActivityIndicator
style={styles.spinner}
size="large"
color="#00ff00"
/>
) : (
<TouchableOpacity onPress={onPressHandler} style={styles.button}>
<Text style={styles.buttonText}>Authenticate</Text>
</TouchableOpacity>
)}
</View>
</SafeAreaView>
<FlashMessage />
</>
)
}

Your app should now look like this:

React Native Basic UI.

The user will begin the SubscriberCheck Authentication by clicking the TouchableOpacity (button). So, we need to set the loading state and create the payload to submit to the server. We do this within the onPressHandler function:

const onPressHandler = async () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
}

Set the loading state to true to give the user a visual cue that works (an HTTP network request) is in progress via our loading indicator. Next, we construct the body object setting the phone_number field to the phoneNumber.

Create a SubscriberCheck using the SubscriberCheck API

With our body request payload ready, we're ready to make requests to our server. First, let's import and setup Axios in our development server is:

import FlashMessage, { showMessage } from 'react-native-flash-message'
import axios from 'axios'
axios.defaults.baseURL = 'https://{subdomain}.{region}.ngrok.io'

Remember to replace https://{subdomain}.{region}.ngrok.io with your development server URL.

Then update the onPressHandler to make a request to create a SubscriberCheck:

const onPressHandler = async () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
try {
const response = await axios.post('/v0.2/subscriber-check', body)
console.log(response.data)
} catch (e) {
setLoading(false)
setError(e.message)
}
}

Here, we make a network request to our app server's SubscriberCheck endpoint to get back the SubscriberCheck URL (check_url) and the check_id, which we'll use in a future step.

Request the SubscriberCheck URL on the Mobile Device over Mobile Data

The next step is requesting the SubscriberCheck URL on the mobile device over mobile data. For this, we'll use the tru.ID React Native SDK.

The first step is to install it via:

npm install @tru_id/tru-sdk-react-native

Note: After installing the SDK, you should restart your Metro server, re-run npm run android, and clean the XCode build to ensure the changes are picked up.

Then add the import to the top of App.js:

import TruSdkReactNative from '@tru_id/tru-sdk-react-native'

The tru.ID React Native SDK openWithDataCellular(checkUrl) function forces the request to the SubscriberCheck URL (check_url) to go over the mobile data connection.

Add the openWithDataCellular call to the onPressHandler:

const onPressHandler = async () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
try {
const response = await axios.post('/v0.2/subscriber-check', body)
console.log(response.data)
const checkResponse = await TruSdkReactNative.openWithDataCellular(
response.data.check_url
);
console.log('check_url request compeleted')
} catch (e) {
setLoading(false)
setError(e.message)
}
}

Complete the SubscriberCheck and get the result via the Application Server

With the check_url request complete, you now need to take the check_url and code received in the final redirect of the check_url, and pass that to the application server. The application server will then make a PATCH request to tru.ID's API in order to complete the SubscriberCheck, and return the response back to the mobile application.

Add a new stateful value called data:

const App = () => {
const [phoneNumber, setPhoneNumber] = React.useState('')
const [loading, setLoading] = React.useState(false)
const [error, setError] = React.useState(null)
const [data, setData] = React.useState(null)
React.useEffect(() => {
...

Make a request to the /v0.2/subscriber-check/exchange-code endpoint to get the result, save it to state with setData, and set loading to false above the line } catch (e) {:

if ('error' in checkResponse) {
console.log(`Error in openWithDataCellular: ${checkResponse.error_description}`)
setLoading(false)
showMessage({
message: 'Verification failed',
type: 'danger',
style: styles.toastContainer,
})
} else if ('http_status' in checkResponse) {
const httpStatus = checkResponse.http_status
if (httpStatus === 200 && checkResponse.response_body !== undefined) {
console.log(`Requesting PhoneCheck URL`)
if ('error' in checkResponse.response_body) {
const body = checkResponse.response_body
console.log(`Error: ${body.error_description}`)
showMessage({
message: `Verification failed: ${body.error_description}`,
type: 'danger',
style: styles.toastContainer,
})
} else {
const body = checkResponse.response_body
try {
const checkStatusRes = await axios.post('/v0.2/subscriber-check/exchange-code', { check_id: body.check_id, code: body.code })
console.log('[CHECK RESULT]:', checkStatusRes.data)
setData(checkStatusRes.data)
setLoading(false)
} catch (error) {
showMessage({
message: `Error retrieving check result: ${error.message}`,
type: 'danger',
style: styles.toastContainer,
})
return;
}
}
} else {
const body = resp.response_body;
showMessage({
message: `Error: ${body.detail}`,
type: 'danger',
style: styles.toastContainer,
})
}
}

With that in place, your app will look as follows when creating the SubscriberCheck, requesting the check_url using the tru.ID SDK, and then retrieving the SubscriberCheck result:

React Native Loading State

Updating the UI with the result

The last thing we need to do is inform users whether or not there is a SubscriberCheck match (i.e., the phone number is verified) and if the SIM has changed recently. For this, we'll use toast notifications.

In your App(), at the top add the following useEffect:.

//check if there is a match, i.e., the phone number has been verified and no_sim_change and render toast UI
React.useEffect(() => {
if (data) {
data.match && data.no_sim_change
? showMessage({
message: 'Phone Verified',
type: 'success',
style: styles.toastContainer,
})
: showMessage({
message: 'Verification failed',
type: 'danger',
style: styles.toastContainer,
})
}
}, [data])

In this React.useEffect function we add data as a dependency, so whenever data changes the effect re-runs. Inside the effect, we check if we have data and subsequently check if we have a match and if the SIM has not changed. If both are confirmed, we render a toast UI with a Phone Verified message (this can be anything you want). Otherwise, we render a toast UI with a Verification failed. Please Try Again Later message.

Your UI, if the SubscriberCheck is successful, should look like this:

React Native Successful Authentication

Wrapping Up

There you have it: you’ve successfully integrated tru.ID SubscriberCheck with your React Native application to build a passwordless signup or login flow. The application verifies a phone number and determines if the SIM card for the device has changed recently.

Resources

Get in touch

If you have any questions, contact help@tru.id.

Download our Developer Console mobile app
Made withacross the 🌍
© 2024 4Auth Limited. All rights reserved. tru.ID is the trading name of 4Auth Limited.