React Native

OTP Authentication for React Native using Cloud Functions

Introduction

In this guide, you will learn how to use a Parse Cloud Code function to integrate Twilio Verify API in a React Native App to enable an OTP login feature. Cloud Code functions are the perfect place for this type of operations since it allows you to handle sensitive data and use other APIs outside of your application.

In this guide you will also check how to implement a React Native component using the OTP integration, improving our previous guide User Email Verification example.

At any time, you can access this project via our GitHub repositories to checkout the styles and complete code.

Prerequisites

To complete this tutorial, you will need:

Goal

Integrate Twilio Verify API using Parse Cloud Code functions on Back4App and use it in a React Native App.

Step 1 - Setting up Twilio Verify

Twilio is a well-known 2FA token and OTP service provider, used by a wide range of companies nowadays. The Verify API is a simplified API set to help developers to verify phones, emails and to perform passwordless logins with no hassle. In this guide, you will be using the SMS and email verification methods to enable your application to have an OTP feature. We will be following the official Verify docs, so reference it if any step becomes confused to you.

After creating your Twilio and SendGrid accounts (SendGrid is needed for sending automated emails containing your user tokens), write down the following identifiers, that can be retrieved from each services dashboard:

  • Twilio’s account SID and auth token;
  • SendGrid ID and a simple email template ID.

Now, create a new Verify service in Twilio Verify dashboard, which is a set of configurations that will manage our OTP requests and verifications. Make sure to write down your service SID here. If you want, you can also enable email code sending by adding your SendGrid keys by creating a new email integration within the Verify service dashboard.

That’s it, all setup is done and we may now proceed to create our Cloud Code functions.

Step 2 - Integrating via Cloud Code Functions

As discussed before, using Cloud Code functions in your application enables great flexibility in your code, making it possible to detach reusable methods from your app and to better control their behavior. You can check or review how to use them in our Cloud functions starter guide.

Let´s create our first cloud function called requestOTP, in which an OTP will be created and sent to the user through the chosen method. The function needs to receive two parameters, userData containing an email or phone number and verificationType, specifying which verification method to be used, email or sms.

Here is the function code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Define Twilio keys and require the library helper
const accountSid = 'YOUR_TWILIO_ACCOUNT_SID_HERE';
const authToken = 'YOUR_TWILIO_AUTH_TOKEN_HERE';
const client = require('twilio')(accountSid, authToken);

// Request OTP
Parse.Cloud.define('requestOTP', async (request) => {
  const userData = request.params.userData;
  const verificationType = request.params.verificationType;
  let verification_request = await client.verify
    .services('YOUR_TWILIO_VERIFY_SERVICE_ID_HERE')
    .verifications.create({ to: userData, channel: verificationType });
  return { status: verification_request.status, error: '' };
});

Note that at the top we defined our Twilio API keys and also imported the helper library. Back4App’s Parse Server implementation provides us access to the Twilio library from default, so you don’t need to install it on your server.

The second and last cloud function is called verifyOTP, it verifies your user token in Twilio’s Verify and logs him in automatically, creating a session and passing its ID back to your application, so you can log in with no password from there. There are four required parameters, being the first two the same ones from the previous function, with the addition of userToken, containing the informed verifying token, and also userInstallationId, which will be explained later on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Since we need in UUID v4 id for creating a new session, this function
// mocks the creation of one without the need to import the `uuidv4` module
// For a more robust solution, consider using the uuid module, which uses
// higher quality RNG APIs.
// Adapted from https://stackoverflow.com/a/2117523
function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

// Verify OTP
Parse.Cloud.define('verifyOTP', async (request) => {
  const { userData, verificationType, userToken, userInstallationId } =
    request.params;
  let verification_check = await client.verify
    .services('YOUR_TWILIO_VERIFY_SERVICE_ID_HERE')
    .verificationChecks.create({ to: userData, code: userToken });
  // Status can be 'approved' if correct or 'pending' if incorrect
  if (verification_check.status === 'approved') {
    try {
      // Get user to login
      let user = null;
      if (verificationType === 'sms') {
        user = await new Parse.Query(Parse.User)
          .equalTo('phoneNumber', userData)
          .first({ useMasterKey: true });
      } else {
        user = await new Parse.Query(Parse.User)
          .equalTo('email', userData)
          .first({ useMasterKey: true });
      }
      // Create new session for login use in
      // Manually create session (without using Parse.Session because of it's update restrictions)
      // Adapted from https://stackoverflow.com/a/67432715
      let session = new Parse.Object('_Session', {
        user: user,
        installationId: userInstallationId,
        sessionToken: `r:${uuidv4()}`,
      });
      session = await session.save(undefined, { useMasterKey: true });
      return { sessionId: session.get('sessionToken') };
    } catch (error) {
      console.log(error);
      return { error: `${error}` };
    }
  }
  return { error: 'Could not validate your token or account! Try again!' };
});

Make sure to deploy these functions in your Parse Server before moving to the next step.

Step 3 - Creating an OTP feature in React Native

Let’s now use the same project example from the User Email Verification guide as a base and add some changes to it enabling the new OTP feature. We recommend downloading the project example and setting it up before continuing with the guide.

First, to allow users to log in using their phone number, add a new input field and add the value to the user registration saving method in the UserRegistration.js (or UserRegistration.tsx) file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import React, {useState} from 'react';
import {
  Alert,
  Image,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';
import Parse from 'parse/react-native';
import {useNavigation} from '@react-navigation/native';
import {StackActions} from '@react-navigation/native';
import Styles from './Styles';

export const UserRegistration = () => {
  const navigation = useNavigation();

  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [email, setEmail] = useState('');
  const [phoneNumber, setPhoneNumber] = useState('');

  const doUserSignUp = async function () {
    // Note that this values come from state variables that we've declared before
    const usernameValue = username;
    const passwordValue = password;
    const emailValue = email;
    const phoneNumberValue = phoneNumber;
    try {
      // Since the signUp method returns a Promise, we need to call it using await
      // Note that now you are setting the user email value as well
      let createdUser = await Parse.User.signUp(usernameValue, passwordValue, {
        email: emailValue,
        phoneNumber: phoneNumberValue,
      });

      // Parse.User.signUp returns the already created ParseUser object if successful
      Alert.alert(
        'Success!',
        `User ${createdUser.get(
          'username',
        )} was successfully created! Verify your email to login`,
      );
      // Since email verification is now required, make sure to log out
      // the new user, so any Session created is cleared and the user can
      // safely log in again after verifying
      await Parse.User.logOut();
      // Go back to the login page
      navigation.dispatch(StackActions.popToTop());
      return true;
    } catch (error) {
      // signUp can fail if any parameter is blank or failed an uniqueness check on the server
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <View style={Styles.login_wrapper}>
      <View style={Styles.form}>
        <TextInput
          style={Styles.form_input}
          value={username}
          placeholder={'Username'}
          onChangeText={(text) => setUsername(text)}
          autoCapitalize={'none'}
          keyboardType={'email-address'}
        />
        <TextInput
          style={Styles.form_input}
          value={email}
          placeholder={'Email'}
          onChangeText={(text) => setEmail(text)}
          autoCapitalize={'none'}
          keyboardType={'email-address'}
        />
        <TextInput
          style={Styles.form_input}
          value={phoneNumber}
          placeholder={'Phone (international format +15017122661)'}
          onChangeText={(text) => setPhoneNumber(text)}
          autoCapitalize={'none'}
          keyboardType={'phone-pad'}
        />
        <TextInput
          style={Styles.form_input}
          value={password}
          placeholder={'Password'}
          secureTextEntry
          onChangeText={(text) => setPassword(text)}
        />
        <TouchableOpacity onPress={() => doUserSignUp()}>
          <View style={Styles.button}>
            <Text style={Styles.button_label}>{'Sign Up'}</Text>
          </View>
        </TouchableOpacity>
      </View>
      <View style={Styles.login_social}>
        <View style={Styles.login_social_separator}>
          <View style={Styles.login_social_separator_line} />
          <Text style={Styles.login_social_separator_text}>{'or'}</Text>
          <View style={Styles.login_social_separator_line} />
        </View>
        <View style={Styles.login_social_buttons}>
          <TouchableOpacity>
            <View
              style={[
                Styles.login_social_button,
                Styles.login_social_facebook,
              ]}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-facebook.png')}
              />
            </View>
          </TouchableOpacity>
          <TouchableOpacity>
            <View style={Styles.login_social_button}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-google.png')}
              />
            </View>
          </TouchableOpacity>
          <TouchableOpacity>
            <View style={Styles.login_social_button}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-apple.png')}
              />
            </View>
          </TouchableOpacity>
        </View>
      </View>
      <>
        <TouchableOpacity onPress={() => navigation.navigate('Login')}>
          <Text style={Styles.login_footer_text}>
            {'Already have an account? '}
            <Text style={Styles.login_footer_link}>{'Log In'}</Text>
          </Text>
        </TouchableOpacity>
      </>
    </View>
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import React, {FC, ReactElement, useState} from 'react';
import {
  Alert,
  Image,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';
import Parse from 'parse/react-native';
import {useNavigation} from '@react-navigation/native';
import {StackActions} from '@react-navigation/native';
import Styles from './Styles';

export const UserRegistration: FC<{}> = ({}): ReactElement => {
  const navigation = useNavigation();

  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [email, setEmail] = useState('');
  const [phoneNumber, setPhoneNumber] = useState('');

  const doUserSignUp = async function (): Promise<boolean> {
    // Note that this values come from state variables that we've declared before
    const usernameValue: string = username;
    const passwordValue: string = password;
    const emailValue: string = email;
    const phoneNumberValue: string = phoneNumber;
    try {
      // Since the signUp method returns a Promise, we need to call it using await
      // Note that now you are setting the user email value as well
      let createdUser = await Parse.User.signUp(usernameValue, passwordValue, {
        email: emailValue,
        phoneNumber: phoneNumberValue,
      });

      // Parse.User.signUp returns the already created ParseUser object if successful
      Alert.alert(
        'Success!',
        `User ${createdUser.get(
          'username',
        )} was successfully created! Verify your email to login`,
      );
      // Since email verification is now required, make sure to log out
      // the new user, so any Session created is cleared and the user can
      // safely log in again after verifying
      await Parse.User.logOut();
      // Go back to the login page
      navigation.dispatch(StackActions.popToTop());
      return true;
    } catch (error: object) {
      // signUp can fail if any parameter is blank or failed an uniqueness check on the server
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <View style={Styles.login_wrapper}>
      <View style={Styles.form}>
        <TextInput
          style={Styles.form_input}
          value={username}
          placeholder={'Username'}
          onChangeText={(text) => setUsername(text)}
          autoCapitalize={'none'}
          keyboardType={'email-address'}
        />
        <TextInput
          style={Styles.form_input}
          value={email}
          placeholder={'Email'}
          onChangeText={(text) => setEmail(text)}
          autoCapitalize={'none'}
          keyboardType={'email-address'}
        />
        <TextInput
          style={Styles.form_input}
          value={phoneNumber}
          placeholder={'Phone (international format +15017122661)'}
          onChangeText={(text) => setPhoneNumber(text)}
          autoCapitalize={'none'}
          keyboardType={'phone-pad'}
        />
        <TextInput
          style={Styles.form_input}
          value={password}
          placeholder={'Password'}
          secureTextEntry
          onChangeText={(text) => setPassword(text)}
        />
        <TouchableOpacity onPress={() => doUserSignUp()}>
          <View style={Styles.button}>
            <Text style={Styles.button_label}>{'Sign Up'}</Text>
          </View>
        </TouchableOpacity>
      </View>
      <View style={Styles.login_social}>
        <View style={Styles.login_social_separator}>
          <View style={Styles.login_social_separator_line} />
          <Text style={Styles.login_social_separator_text}>{'or'}</Text>
          <View style={Styles.login_social_separator_line} />
        </View>
        <View style={Styles.login_social_buttons}>
          <TouchableOpacity>
            <View
              style={[
                Styles.login_social_button,
                Styles.login_social_facebook,
              ]}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-facebook.png')}
              />
            </View>
          </TouchableOpacity>
          <TouchableOpacity>
            <View style={Styles.login_social_button}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-google.png')}
              />
            </View>
          </TouchableOpacity>
          <TouchableOpacity>
            <View style={Styles.login_social_button}>
              <Image
                style={Styles.login_social_icon}
                source={require('./assets/icon-apple.png')}
              />
            </View>
          </TouchableOpacity>
        </View>
      </View>
      <>
        <TouchableOpacity onPress={() => navigation.navigate('Login')}>
          <Text style={Styles.login_footer_text}>
            {'Already have an account? '}
            <Text style={Styles.login_footer_link}>{'Log In'}</Text>
          </Text>
        </TouchableOpacity>
      </>
    </View>
  );
};

Let’s now create a new file containing the new UserOTP screen, which will handle all the OTP processes. The screen will have two input fields, being the first one for your user to provide the means to get the OTP (email address or phone number). The other input field, hidden before submitting the OTP request, will contain the user received token. Here is the full UserOTP.js (or UserOTP.tsx) code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import React, {useState} from 'react';
import {Alert, Text, TextInput, TouchableOpacity, View} from 'react-native';
import Parse from 'parse/react-native';
import {useNavigation} from '@react-navigation/native';
import Styles from './Styles';

export const UserOTP = () => {
  const navigation = useNavigation();

  const [userData, setUserData] = useState('');
  const [userToken, setUserToken] = useState('');
  const [tokenRequested, setTokenRequested] = useState(false);

  const requestOTP = async function () {
    // Note that this values come from state variables that we've declared before
    const userDataValue = userData;
    // Check if value is an email if it contains @. Note that in a real
    // app you need a much better validator for this field
    const verificationType =
      userDataValue.includes('@') === true ? 'email' : 'sms';
    // We need to call it using await
    try {
      await Parse.Cloud.run('requestOTP', {
        userData: userDataValue,
        verificationType: verificationType,
      });
      // Show token input field
      setTokenRequested(true);
      Alert.alert('Success!', `Token requested via ${verificationType}!`);
      return true;
    } catch (error) {
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const verifyOTP = async function () {
    // Note that this values come from state variables that we've declared before
    const userDataValue = userData;
    const userTokenValue = userToken;
    // Check if value is an email if it contains @. Note that in a real
    // app you need a much better validator for this field
    const verificationType =
      userDataValue.includes('@') === true ? 'email' : 'sms';
    // We need the installation id to allow cloud code to create
    // a new session and login user without password
    const parseInstallationId = await Parse._getInstallationId();
    // We need to call it using await
    try {
      // Verify OTP, if successful, returns a sessionId
      let response = await Parse.Cloud.run('verifyOTP', {
        userData: userDataValue,
        verificationType: verificationType,
        userToken: userTokenValue,
        parseInstallationId: parseInstallationId,
      });
      if (response.sessionId !== undefined) {
        // Use generated sessionId to become a user,
        // logging in without needing to inform password and username
        await Parse.User.become(response.sessionId);
        const loggedInUser= await Parse.User.currentAsync();
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get('username')} has successfully signed in!`,
        );
        // Navigation.navigate takes the user to the home screen
        navigation.navigate('Home');
        return true;
      } else {
        throw response;
      }
    } catch (error) {
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <View style={Styles.login_wrapper}>
      {tokenRequested === false ? (
        <View style={Styles.form}>
          <TextInput
            style={Styles.form_input}
            value={userData}
            placeholder={'Email or mobile phone number'}
            onChangeText={(text) => setUserData(text)}
            autoCapitalize={'none'}
            keyboardType={'email-address'}
          />
          <TouchableOpacity onPress={() => requestOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Request OTP'}</Text>
            </View>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={Styles.form}>
          <Text>{'Inform the received token to proceed'}</Text>
          <TextInput
            style={Styles.form_input}
            value={userToken}
            placeholder={'Token (6 digits)'}
            onChangeText={(text) => setUserToken(text)}
            autoCapitalize={'none'}
            keyboardType={'default'}
          />
          <TouchableOpacity onPress={() => verifyOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Verify'}</Text>
            </View>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => requestOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Resend token'}</Text>
            </View>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import React, {FC, ReactElement, useState} from 'react';
import {Alert, Text, TextInput, TouchableOpacity, View} from 'react-native';
import Parse from 'parse/react-native';
import {useNavigation} from '@react-navigation/native';
import Styles from './Styles';

export const UserOTP: FC<{}> = ({}): ReactElement => {
  const navigation = useNavigation();

  const [userData, setUserData] = useState('');
  const [userToken, setUserToken] = useState('');
  const [tokenRequested, setTokenRequested] = useState(false);

  const requestOTP = async function (): Promise<boolean> {
    // Note that this values come from state variables that we've declared before
    const userDataValue: string = userData;
    // Check if value is an email if it contains @. Note that in a real
    // app you need a much better validator for this field
    const verificationType: string =
      userDataValue.includes('@') === true ? 'email' : 'sms';
    // We need to call it using await
    try {
      await Parse.Cloud.run('requestOTP', {
        userData: userDataValue,
        verificationType: verificationType,
      });
      // Show token input field
      setTokenRequested(true);
      Alert.alert('Success!', `Token requested via ${verificationType}!`);
      return true;
    } catch (error) {
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const verifyOTP = async function (): Promise<Boolean> {
    // Note that this values come from state variables that we've declared before
    const userDataValue: string = userData;
    const userTokenValue: string = userToken;
    // Check if value is an email if it contains @. Note that in a real
    // app you need a much better validator for this field
    const verificationType: string =
      userDataValue.includes('@') === true ? 'email' : 'sms';
    // We need the installation id to allow cloud code to create
    // a new session and login user without password; this is obtained
    // using a static method from Parse
    const parseInstallationId: string = await Parse._getInstallationId();
    // We need to call it using await
    try {
      // Verify OTP, if successful, returns a sessionId
      let response: object = await Parse.Cloud.run('verifyOTP', {
        userData: userDataValue,
        verificationType: verificationType,
        userToken: userTokenValue,
        parseInstallationId: parseInstallationId,
      });
      if (response.sessionId !== undefined) {
        // Use generated sessionId to become a user,
        // logging in without needing to inform password and username
        await Parse.User.become(response.sessionId);
        const loggedInUser: Parse.User = await Parse.User.currentAsync();
        Alert.alert(
          'Success!',
          `User ${loggedInUser.get('username')} has successfully signed in!`,
        );
        // Navigation.navigate takes the user to the home screen
        navigation.navigate('Home');
        return true;
      } else {
        throw response;
      }
    } catch (error) {
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <View style={Styles.login_wrapper}>
      {tokenRequested === false ? (
        <View style={Styles.form}>
          <TextInput
            style={Styles.form_input}
            value={userData}
            placeholder={'Email or mobile phone number'}
            onChangeText={(text) => setUserData(text)}
            autoCapitalize={'none'}
            keyboardType={'email-address'}
          />
          <TouchableOpacity onPress={() => requestOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Request OTP'}</Text>
            </View>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={Styles.form}>
          <Text>{'Inform the received token to proceed'}</Text>
          <TextInput
            style={Styles.form_input}
            value={userToken}
            placeholder={'Token (6 digits)'}
            onChangeText={(text) => setUserToken(text)}
            autoCapitalize={'none'}
            keyboardType={'default'}
          />
          <TouchableOpacity onPress={() => verifyOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Verify'}</Text>
            </View>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => requestOTP()}>
            <View style={Styles.button}>
              <Text style={Styles.button_label}>{'Resend token'}</Text>
            </View>
          </TouchableOpacity>
        </View>
      )}
    </View>
  );
};

Take a closer look at the requestOTP and verifyOTP functions, which are responsible for calling the respective Cloud Code functions and validating their response. More detail on how they work can be inspected in the code comments.

After creating the new screen, import and declare it in your App.js (or App.tsx). After that, add a new button in your UserLogin.js (or UserLogin.tsx) file, enabling your user to navigate to the OTP screen.

Step 4 - Testing the New OTP Feature

Let’s now test our changes to the app. First, register a new user containing a valid email and phone number. Make sure to use the international notation (E.164) format in the phone number (e.g.: +14155552671).

React Native Back4App

Now, navigate to the OTP screen from the login screen and inform the same email or phone number as before. Click on the request button and you should get a message like this, changing the active input on your screen.

React Native Back4App

If you informed an email address, you should receive an email containing the OTP token; if a phone number was passed, you will get an SMS text message on your mobile phone. The email should contain a message like this, depending on how you set up the SendGrid template.

React Native Back4App

Inform the OTP token and click on verify. If everything went well, you should now be at the home screen with the following message:

React Native Back4App

Conclusion

At the end of this guide, you learned how to use Parse Cloud Code functions to integrate third-party services in your React Native application. In the next guide, you will learn how to work with Users in Parse.