tru.ID logo
LoginSignup

Passwordless Mobile Authentication and SIM Swap Detection with React Native

In this tutorial, you'll learn how to use add passwordless authentication to a React Native application by using SubscriberCheck to verify the phone number associated with the SIM card on a mobile device, and check if that SIM card has changed recently in order to detect potential SIM swap attacks.

Timothy Ogbemudia

Developer Experience Engineer

Last updated: 25 March 2021

tutorial cover image

Before we begin, let's talk about why you would 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 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 spplication server
  7. Application server gets the SubscriberCheck result from SubscriberCheck 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

Get setup with tru.ID

Sign up for a tru.ID account which comes with some free credit. Then install the tru.ID CLI:

$ npm install -g @tru_id/cli

Run tru setup:credentials using the credentials from the tru.ID console:

$ tru setup:credentials {YOUR_CLIENT_ID} {YOUR_CLIENT_SECRET} EU

Install the CLI development server plugin:

$ tru plugins:install @tru_id/cli-plugin-dev-server

Note: If you'd prefer to clone the development server and run in manually you can get the dev-server source on GitHub.

Create a new tru.ID project:

$ tru projects:create rn-auth

This will save a tru.json tru.ID project configuration to ./rn-auth/tru.json.

Run the development server, pointing it to the directory containing the newly created project configuration. This will also open up a localtunnel to your development server, making it publicly accessible to the Internet, so that your mobile phone can access it when only connected to mobile data.

$ tru server -t --project-dir ./rn-auth

Open up the URL that is shown in the terminal, which will be in the format https://{subdomain}.loca.lt, in your desktop web browser, to check that it is accessible.

With the development server setup we can move on to building the React Native application.

Getting Started

In order 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 --single-branch 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 for checking if any work is in progress via a loading variable or if 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 = () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
}

Set the loading state to true to give the user a visual cue that work (a 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 to know where our development server is:

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

Remember to replace https://{subdomain}.loca.lt with your development server URL.

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

const onPressHandler = () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
try {
const response = await axios.post('/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 in order 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 TruSDK from '@tru_id/tru-sdk-react-native'

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

Add the openCheckUrl call to the onPressHandler:

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

Get the SubscriberCheck Result via the Application Server

With the check_url request complete, we can now make a request to the application server to get the SubscriberCheck result.

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 subscriber-check/{check_id} endpoint to get the result, save it to state with setData and set loading to false:

const onPressHandler = async () => {
setLoading(true)
const body = {
phone_number: phoneNumber,
}
console.log(body)
try {
const response = await axios.post('/subscriber-check', body)
console.log(response.data)
await TruSDK.openCheckUrl(response.data.check_url)
const resp = await axios.get(`/subscriber-check/${response.data.check_id}`)
console.log(resp.data)
setData(resp.data)
setLoading(false)
} catch (e) {
setLoading(false)
setError(e.message)
}
}

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.

Update App.js with the following within your App():

//check if there is a match i.e. 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 of these 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, get in touch via help@tru.id.

tru.ID logo

Platform

Docs

DON'T MISS A BEAT — STAY ON THE DOT!

Keep current with industry news and updates from tru.ID.

Follow us on:

Made with ❤️ across the 🌍

© 2021 4Auth Limited. All rights reserved. tru.ID is the trading name of 4Auth Limited.