Autenticação OTP no React Native com Twilio e Cloud Code
12 min
autenticação otp para react native usando funções em nuvem introdução neste guia, você aprenderá como usar uma função de código em nuvem do parse para integrar a api twilio verify em um aplicativo react native para habilitar um recurso de login otp as funções de código em nuvem são o lugar perfeito para esse tipo de operação, pois permitem que você manipule dados sensíveis e use outras apis fora de sua aplicação neste guia, você também verá como implementar um componente react native usando a integração otp, melhorando nosso exemplo anterior de verificação de email do usuário a qualquer momento, você pode acessar o projeto android completo construído com este tutorial em nossos repositórios do github repositório de exemplo em kotlin https //github com/templates back4app/android parse sdk kotlin repositório de exemplo em java https //github com/templates back4app/android parse sdk java pré requisitos para completar este tutorial, você precisará um aplicativo react native criado e conectado ao back4app https //www back4app com/docs/react native/parse sdk/react native sdk entender como implantar uma função em nuvem https //www back4app com/docs/get started/cloud functions no back4app uma conta ativa no twilio, você pode criar uma conta de teste gratuita aqui https //login twilio com/u/signup?state=hkfo2sbwngzlcuy5cjj3wgfzahqwcnizme5kqtvpvg51yjbzu6fur3vuaxzlcnnhbc1sb2dpbqn0awtzihg1lvrwtxpraupht3d4mghzwunxqm5wqi0xddm1wdhlo2npznkgtw05m1lttdvsclpmnzdobulkzfi3qktzyjzpoxv1cks uma conta ativa no sendgrid, você pode criar uma conta de teste gratuita aqui https //login twilio com/u/signup?state=hkfo2sbfztlfrwntc2tflwrdqumtqujjc3pgnufnzfrgdlntdqfur3vuaxzlcnnhbc1sb2dpbqn0awtzihm3ek1jewjougvbn3dndljsc0dlytywlwtvbwppq0nno2npznkgr244uwmyz1foa2trz0llt2s4qljqrwz5ewnomtu2vuk objetivo integrar a api twilio verify usando funções de código em nuvem parse no back4app e usá la em um aplicativo react native 1 configurando o twilio verify twilio é um provedor de serviços de token 2fa e otp bem conhecido, usado por uma ampla gama de empresas atualmente a api verify é um conjunto de apis simplificadas para ajudar os desenvolvedores a verificar telefones, e mails e realizar logins sem senha sem complicações neste guia, você usará os métodos de verificação por sms e e mail para permitir que seu aplicativo tenha um recurso otp seguiremos a documentação oficial do verify docs https //www twilio com/docs/verify/api , então consulte a se algum passo ficar confuso para você após criar suas contas twilio e sendgrid (sendgrid é necessário para enviar e mails automatizados contendo seus tokens de usuário), anote os seguintes identificadores, que podem ser recuperados do painel de cada serviço sid da conta twilio e token de autenticação; id do sendgrid e um id de modelo de e mail simples agora, crie um novo serviço de verificação no twilio verify painel https //www twilio com/console/verify/dashboard , que é um conjunto de configurações que gerenciará nossas solicitações e verificações de otp certifique se de anotar seu sid de serviço aqui se desejar, você também pode habilitar o envio de código por e mail adicionando suas chaves do sendgrid criando uma nova integração de e mail dentro do serviço de verificação painel https //www twilio com/docs/verify/email é isso, toda a configuração está feita e agora podemos prosseguir para criar nossas funções de cloud code 2 integrando via funções de cloud code como discutido anteriormente, usar funções de cloud code em seu aplicativo permite uma grande flexibilidade em seu código, tornando possível desacoplar métodos reutilizáveis do seu aplicativo e controlar melhor seu comportamento você pode verificar ou revisar como usá los em nosso guia inicial de funções de cloud https //www back4app com/docs/get started/cloud functions vamos criar nossa primeira função em nuvem chamada \<font color="#2166ae">requestotp\</font> , na qual um otp será criado e enviado ao usuário através do método escolhido a função precisa receber dois parâmetros, \<font color="#2166ae">userdata\</font> contendo um e mail ou número de telefone e \<font color="#2166ae">verificationtype\</font> , especificando qual método de verificação deve ser usado, \<font color="#2166ae">email\</font> ou \<font color="#2166ae">sms\</font> }], aqui está o código da função 1 // define twilio keys and require the library helper 2 const accountsid = 'your twilio account sid here'; 3 const authtoken = 'your twilio auth token here'; 4 const client = require('twilio')(accountsid, authtoken); 5	 6 // request otp 7 parse cloud define('requestotp', async (request) => { 8 const userdata = request params userdata; 9 const verificationtype = request params verificationtype; 10 let verification request = await client verify 11 services('your twilio verify service id here') 12 verifications create({ to userdata, channel verificationtype }); 13 return { status verification request status, error '' }; 14 }); observe que no topo definimos nossas chaves da api do twilio e também importamos a biblioteca auxiliar a implementação do parse server do back4app nos fornece acesso à biblioteca do twilio por padrão, então você não precisa instalá la em seu servidor a segunda e última função em nuvem é chamada \<font color="#2166ae">verifyotp\</font> , ela verifica o token do seu usuário no verify do twilio e o faz logar automaticamente, criando uma sessão e passando seu id de volta para sua aplicação, para que você possa fazer login sem senha a partir daí existem quatro parâmetros obrigatórios, sendo os dois primeiros os mesmos da função anterior, com a adição de \<font color="#2166ae">usertoken\</font> , contendo o token de verificação informado, e também \<font color="#2166ae">userinstallationid\</font> , que será explicado mais adiante 1 // since we need in uuid v4 id for creating a new session, this function 2 // mocks the creation of one without the need to import the `uuidv4` module 3 // for a more robust solution, consider using the uuid module, which uses 4 // higher quality rng apis 5 // adapted from https //stackoverflow\ com/a/2117523 6 function uuidv4() { 7 return 'xxxxxxxx xxxx 4xxx yxxx xxxxxxxxxxxx' replace(/\[xy]/g, function (c) { 8 var r = (math random() 16) | 0, 9 v = c == 'x' ? r (r & 0x3) | 0x8; 10 return v tostring(16); 11 }); 12 } 13	 14 // verify otp 15 parse cloud define('verifyotp', async (request) => { 16 const { userdata, verificationtype, usertoken, userinstallationid } = 17 request params; 18 let verification check = await client verify 19 services('your twilio verify service id here') 20 verificationchecks create({ to userdata, code usertoken }); 21 // status can be 'approved' if correct or 'pending' if incorrect 22 if (verification check status === 'approved') { 23 try { 24 // get user to login 25 let user = null; 26 if (verificationtype === 'sms') { 27 user = await new parse query(parse user) 28 equalto('phonenumber', userdata) 29 first({ usemasterkey true }); 30 } else { 31 user = await new parse query(parse user) 32 equalto('email', userdata) 33 first({ usemasterkey true }); 34 } 35 // create new session for login use in 36 // manually create session (without using parse session because of it's update restrictions) 37 // adapted from https //stackoverflow\ com/a/67432715 38 let session = new parse object(' session', { 39 user user, 40 installationid userinstallationid, 41 sessiontoken `r ${uuidv4()}`, 42 }); 43 session = await session save(undefined, { usemasterkey true }); 44 return { sessionid session get('sessiontoken') }; 45 } catch (error) { 46 console log(error); 47 return { error `${error}` }; 48 } 49 } 50 return { error 'could not validate your token or account! try again!' }; 51 }); certifique se de implantar essas funções em seu parse server antes de passar para a próxima etapa 3 criando um recurso otp no react native vamos agora usar o mesmo exemplo de projeto da verificação de email do usuário guia https //www back4app com/docs/react native/parse sdk/working with users/react native email verification como base e adicionar algumas mudanças para habilitar o novo recurso de otp recomendamos baixar o exemplo do projeto e configurá lo antes de continuar com o guia primeiro, para permitir que os usuários façam login usando seu número de telefone, adicione um novo campo de entrada e adicione o valor ao método de salvamento de registro do usuário no \<font color="#2166ae">userregistration js\</font> (ou \<font color="#2166ae">userregistration tsx\</font> ) arquivo javascript 1 import react, {usestate} from 'react'; 2 import { 3 alert, 4 image, 5 text, 6 textinput, 7 touchableopacity, 8 view, 9 } from 'react native'; 10 import parse from 'parse/react native'; 11 import {usenavigation} from '@react navigation/native'; 12 import {stackactions} from '@react navigation/native'; 13 import styles from ' /styles'; 14	 15 export const userregistration = () => { 16 const navigation = usenavigation(); 17	 18 const \[username, setusername] = usestate(''); 19 const \[password, setpassword] = usestate(''); 20 const \[email, setemail] = usestate(''); 21 const \[phonenumber, setphonenumber] = usestate(''); 22	 23 const dousersignup = async function () { 24 // note that this values come from state variables that we've declared before 25 const usernamevalue = username; 26 const passwordvalue = password; 27 const emailvalue = email; 28 const phonenumbervalue = phonenumber; 29 try { 30 // since the signup method returns a promise, we need to call it using await 31 // note that now you are setting the user email value as well 32 let createduser = await parse user signup(usernamevalue, passwordvalue, { 33 email emailvalue, 34 phonenumber phonenumbervalue, 35 }); 36	 37 // parse user signup returns the already created parseuser object if successful 38 alert alert( 39 'success!', 40 `user ${createduser get( 41 'username', 42 )} was successfully created! verify your email to login`, 43 ); 44 // since email verification is now required, make sure to log out 45 // the new user, so any session created is cleared and the user can 46 // safely log in again after verifying 47 await parse user logout(); 48 // go back to the login page 49 navigation dispatch(stackactions poptotop()); 50 return true; 51 } catch (error) { 52 // signup can fail if any parameter is blank or failed an uniqueness check on the server 53 alert alert('error!', error message); 54 return false; 55 } 56 }; 57	 58 return ( 59 \<view style={styles login wrapper}> 60 \<view style={styles form}> 61 \<textinput 62 style={styles form input} 63 value={username} 64 placeholder={'username'} 65 onchangetext={(text) => setusername(text)} 66 autocapitalize={'none'} 67 keyboardtype={'email address'} 68 /> 69 \<textinput 70 style={styles form input} 71 value={email} 72 placeholder={'email'} 73 onchangetext={(text) => setemail(text)} 74 autocapitalize={'none'} 75 keyboardtype={'email address'} 76 /> 77 \<textinput 78 style={styles form input} 79 value={phonenumber} 80 placeholder={'phone (international format +15017122661)'} 81 onchangetext={(text) => setphonenumber(text)} 82 autocapitalize={'none'} 83 keyboardtype={'phone pad'} 84 /> 85 \<textinput 86 style={styles form input} 87 value={password} 88 placeholder={'password'} 89 securetextentry 90 onchangetext={(text) => setpassword(text)} 91 /> 92 \<touchableopacity onpress={() => dousersignup()}> 93 \<view style={styles button}> 94 \<text style={styles button label}>{'sign up'}\</text> 95 \</view> 96 \</touchableopacity> 97 \</view> 98 \<view style={styles login social}> 99 \<view style={styles login social separator}> 100 \<view style={styles login social separator line} /> 101 \<text style={styles login social separator text}>{'or'}\</text> 102 \<view style={styles login social separator line} /> 103 \</view> 104 \<view style={styles login social buttons}> 105 \<touchableopacity> 106 \<view 107 style={\[ 108 styles login social button, 109 styles login social facebook, 110 ]}> 111 \<image 112 style={styles login social icon} 113 source={require(' /assets/icon facebook png')} 114 /> 115 \</view> 116 \</touchableopacity> 117 \<touchableopacity> 118 \<view style={styles login social button}> 119 \<image 120 style={styles login social icon} 121 source={require(' /assets/icon google png')} 122 /> 123 \</view> 124 \</touchableopacity> 125 \<touchableopacity> 126 \<view style={styles login social button}> 127 \<image 128 style={styles login social icon} 129 source={require(' /assets/icon apple png')} 130 /> 131 \</view> 132 \</touchableopacity> 133 \</view> 134 \</view> 135 <> 136 \<touchableopacity onpress={() => navigation navigate('login')}> 137 \<text style={styles login footer text}> 138 {'already have an account? '} 139 \<text style={styles login footer link}>{'log in'}\</text> 140 \</text> 141 \</touchableopacity> 142 \</> 143 \</view> 144 ); 145 };1 import react, {fc, reactelement, usestate} from 'react'; 2 import { 3 alert, 4 image, 5 text, 6 textinput, 7 touchableopacity, 8 view, 9 } from 'react native'; 10 import parse from 'parse/react native'; 11 import {usenavigation} from '@react navigation/native'; 12 import {stackactions} from '@react navigation/native'; 13 import styles from ' /styles'; 14	 15 export const userregistration fc<{}> = ({}) reactelement => { 16 const navigation = usenavigation(); 17	 18 const \[username, setusername] = usestate(''); 19 const \[password, setpassword] = usestate(''); 20 const \[email, setemail] = usestate(''); 21 const \[phonenumber, setphonenumber] = usestate(''); 22	 23 const dousersignup = async function () promise\<boolean> { 24 // note that this values come from state variables that we've declared before 25 const usernamevalue string = username; 26 const passwordvalue string = password; 27 const emailvalue string = email; 28 const phonenumbervalue string = phonenumber; 29 try { 30 // since the signup method returns a promise, we need to call it using await 31 // note that now you are setting the user email value as well 32 let createduser = await parse user signup(usernamevalue, passwordvalue, { 33 email emailvalue, 34 phonenumber phonenumbervalue, 35 }); 36	 37 // parse user signup returns the already created parseuser object if successful 38 alert alert( 39 'success!', 40 `user ${createduser get( 41 'username', 42 )} was successfully created! verify your email to login`, 43 ); 44 // since email verification is now required, make sure to log out 45 // the new user, so any session created is cleared and the user can 46 // safely log in again after verifying 47 await parse user logout(); 48 // go back to the login page 49 navigation dispatch(stackactions poptotop()); 50 return true; 51 } catch (error object) { 52 // signup can fail if any parameter is blank or failed an uniqueness check on the server 53 alert alert('error!', error message); 54 return false; 55 } 56 }; 57	 58 return ( 59 \<view style={styles login wrapper}> 60 \<view style={styles form}> 61 \<textinput 62 style={styles form input} 63 value={username} 64 placeholder={'username'} 65 onchangetext={(text) => setusername(text)} 66 autocapitalize={'none'} 67 keyboardtype={'email address'} 68 /> 69 \<textinput 70 style={styles form input} 71 value={email} 72 placeholder={'email'} 73 onchangetext={(text) => setemail(text)} 74 autocapitalize={'none'} 75 keyboardtype={'email address'} 76 /> 77 \<textinput 78 style={styles form input} 79 value={phonenumber} 80 placeholder={'phone (international format +15017122661)'} 81 onchangetext={(text) => setphonenumber(text)} 82 autocapitalize={'none'} 83 keyboardtype={'phone pad'} 84 /> 85 \<textinput 86 style={styles form input} 87 value={password} 88 placeholder={'password'} 89 securetextentry 90 onchangetext={(text) => setpassword(text)} 91 /> 92 \<touchableopacity onpress={() => dousersignup()}> 93 \<view style={styles button}> 94 \<text style={styles button label}>{'sign up'}\</text> 95 \</view> 96 \</touchableopacity> 97 \</view> 98 \<view style={styles login social}> 99 \<view style={styles login social separator}> 100 \<view style={styles login social separator line} /> 101 \<text style={styles login social separator text}>{'or'}\</text> 102 \<view style={styles login social separator line} /> 103 \</view> 104 \<view style={styles login social buttons}> 105 \<touchableopacity> 106 \<view 107 style={\[ 108 styles login social button, 109 styles login social facebook, 110 ]}> 111 \<image 112 style={styles login social icon} 113 source={require(' /assets/icon facebook png')} 114 /> 115 \</view> 116 \</touchableopacity> 117 \<touchableopacity> 118 \<view style={styles login social button}> 119 \<image 120 style={styles login social icon} 121 source={require(' /assets/icon google png')} 122 /> 123 \</view> 124 \</touchableopacity> 125 \<touchableopacity> 126 \<view style={styles login social button}> 127 \<image 128 style={styles login social icon} 129 source={require(' /assets/icon apple png')} 130 /> 131 \</view> 132 \</touchableopacity> 133 \</view> 134 \</view> 135 <> 136 \<touchableopacity onpress={() => navigation navigate('login')}> 137 \<text style={styles login footer text}> 138 {'already have an account? '} 139 \<text style={styles login footer link}>{'log in'}\</text> 140 \</text> 141 \</touchableopacity> 142 \</> 143 \</view> 144 ); 145 }; vamos agora criar um novo arquivo contendo a nova \<font color="#2166ae">userotp\</font> tela, que irá gerenciar todos os processos de otp a tela terá dois campos de entrada, sendo o primeiro para o seu usuário fornecer os meios para receber o otp (endereço de e mail ou número de telefone) o outro campo de entrada, oculto antes de enviar a solicitação de otp, conterá o token recebido pelo usuário aqui está o código completo de \<font color="#2166ae">userotp js\</font> (ou \<font color="#2166ae">userotp tsx\</font> ) javascript 1 import react, {usestate} from 'react'; 2 import {alert, text, textinput, touchableopacity, view} from 'react native'; 3 import parse from 'parse/react native'; 4 import {usenavigation} from '@react navigation/native'; 5 import styles from ' /styles'; 6	 7 export const userotp = () => { 8 const navigation = usenavigation(); 9	 10 const \[userdata, setuserdata] = usestate(''); 11 const \[usertoken, setusertoken] = usestate(''); 12 const \[tokenrequested, settokenrequested] = usestate(false); 13	 14 const requestotp = async function () { 15 // note that this values come from state variables that we've declared before 16 const userdatavalue = userdata; 17 // check if value is an email if it contains @ note that in a real 18 // app you need a much better validator for this field 19 const verificationtype = 20 userdatavalue includes('@') === true ? 'email' 'sms'; 21 // we need to call it using await 22 try { 23 await parse cloud run('requestotp', { 24 userdata userdatavalue, 25 verificationtype verificationtype, 26 }); 27 // show token input field 28 settokenrequested(true); 29 alert alert('success!', `token requested via ${verificationtype}!`); 30 return true; 31 } catch (error) { 32 alert alert('error!', error message); 33 return false; 34 } 35 }; 36	 37 const verifyotp = async function () { 38 // note that this values come from state variables that we've declared before 39 const userdatavalue = userdata; 40 const usertokenvalue = usertoken; 41 // check if value is an email if it contains @ note that in a real 42 // app you need a much better validator for this field 43 const verificationtype = 44 userdatavalue includes('@') === true ? 'email' 'sms'; 45 // we need the installation id to allow cloud code to create 46 // a new session and login user without password 47 const parseinstallationid = await parse getinstallationid(); 48 // we need to call it using await 49 try { 50 // verify otp, if successful, returns a sessionid 51 let response = await parse cloud run('verifyotp', { 52 userdata userdatavalue, 53 verificationtype verificationtype, 54 usertoken usertokenvalue, 55 parseinstallationid parseinstallationid, 56 }); 57 if (response sessionid !== undefined) { 58 // use generated sessionid to become a user, 59 // logging in without needing to inform password and username 60 await parse user become(response sessionid); 61 const loggedinuser= await parse user currentasync(); 62 alert alert( 63 'success!', 64 `user ${loggedinuser get('username')} has successfully signed in!`, 65 ); 66 // navigation navigate takes the user to the home screen 67 navigation navigate('home'); 68 return true; 69 } else { 70 throw response; 71 } 72 } catch (error) { 73 alert alert('error!', error message); 74 return false; 75 } 76 }; 77	 78 return ( 79 \<view style={styles login wrapper}> 80 {tokenrequested === false ? ( 81 \<view style={styles form}> 82 \<textinput 83 style={styles form input} 84 value={userdata} 85 placeholder={'email or mobile phone number'} 86 onchangetext={(text) => setuserdata(text)} 87 autocapitalize={'none'} 88 keyboardtype={'email address'} 89 /> 90 \<touchableopacity onpress={() => requestotp()}> 91 \<view style={styles button}> 92 \<text style={styles button label}>{'request otp'}\</text> 93 \</view> 94 \</touchableopacity> 95 \</view> 96 ) ( 97 \<view style={styles form}> 98 \<text>{'inform the received token to proceed'}\</text> 99 \<textinput 100 style={styles form input} 101 value={usertoken} 102 placeholder={'token (6 digits)'} 103 onchangetext={(text) => setusertoken(text)} 104 autocapitalize={'none'} 105 keyboardtype={'default'} 106 /> 107 \<touchableopacity onpress={() => verifyotp()}> 108 \<view style={styles button}> 109 \<text style={styles button label}>{'verify'}\</text> 110 \</view> 111 \</touchableopacity> 112 \<touchableopacity onpress={() => requestotp()}> 113 \<view style={styles button}> 114 \<text style={styles button label}>{'resend token'}\</text> 115 \</view> 116 \</touchableopacity> 117 \</view> 118 )} 119 \</view> 120 ); 121 };1 import react, {fc, reactelement, usestate} from 'react'; 2 import {alert, text, textinput, touchableopacity, view} from 'react native'; 3 import parse from 'parse/react native'; 4 import {usenavigation} from '@react navigation/native'; 5 import styles from ' /styles'; 6	 7 export const userotp fc<{}> = ({}) reactelement => { 8 const navigation = usenavigation(); 9	 10 const \[userdata, setuserdata] = usestate(''); 11 const \[usertoken, setusertoken] = usestate(''); 12 const \[tokenrequested, settokenrequested] = usestate(false); 13	 14 const requestotp = async function () promise\<boolean> { 15 // note that this values come from state variables that we've declared before 16 const userdatavalue string = userdata; 17 // check if value is an email if it contains @ note that in a real 18 // app you need a much better validator for this field 19 const verificationtype string = 20 userdatavalue includes('@') === true ? 'email' 'sms'; 21 // we need to call it using await 22 try { 23 await parse cloud run('requestotp', { 24 userdata userdatavalue, 25 verificationtype verificationtype, 26 }); 27 // show token input field 28 settokenrequested(true); 29 alert alert('success!', `token requested via ${verificationtype}!`); 30 return true; 31 } catch (error) { 32 alert alert('error!', error message); 33 return false; 34 } 35 }; 36	 37 const verifyotp = async function () promise\<boolean> { 38 // note that this values come from state variables that we've declared before 39 const userdatavalue string = userdata; 40 const usertokenvalue string = usertoken; 41 // check if value is an email if it contains @ note that in a real 42 // app you need a much better validator for this field 43 const verificationtype string = 44 userdatavalue includes('@') === true ? 'email' 'sms'; 45 // we need the installation id to allow cloud code to create 46 // a new session and login user without password; this is obtained 47 // using a static method from parse 48 const parseinstallationid string = await parse getinstallationid(); 49 // we need to call it using await 50 try { 51 // verify otp, if successful, returns a sessionid 52 let response object = await parse cloud run('verifyotp', { 53 userdata userdatavalue, 54 verificationtype verificationtype, 55 usertoken usertokenvalue, 56 parseinstallationid parseinstallationid, 57 }); 58 if (response sessionid !== undefined) { 59 // use generated sessionid to become a user, 60 // logging in without needing to inform password and username 61 await parse user become(response sessionid); 62 const loggedinuser parse user = await parse user currentasync(); 63 alert alert( 64 'success!', 65 `user ${loggedinuser get('username')} has successfully signed in!`, 66 ); 67 // navigation navigate takes the user to the home screen 68 navigation navigate('home'); 69 return true; 70 } else { 71 throw response; 72 } 73 } catch (error) { 74 alert alert('error!', error message); 75 return false; 76 } 77 }; 78	 79 return ( 80 \<view style={styles login wrapper}> 81 {tokenrequested === false ? ( 82 \<view style={styles form}> 83 \<textinput 84 style={styles form input} 85 value={userdata} 86 placeholder={'email or mobile phone number'} 87 onchangetext={(text) => setuserdata(text)} 88 autocapitalize={'none'} 89 keyboardtype={'email address'} 90 /> 91 \<touchableopacity onpress={() => requestotp()}> 92 \<view style={styles button}> 93 \<text style={styles button label}>{'request otp'}\</text> 94 \</view> 95 \</touchableopacity> 96 \</view> 97 ) ( 98 \<view style={styles form}> 99 \<text>{'inform the received token to proceed'}\</text> 100 \<textinput 101 style={styles form input} 102 value={usertoken} 103 placeholder={'token (6 digits)'} 104 onchangetext={(text) => setusertoken(text)} 105 autocapitalize={'none'} 106 keyboardtype={'default'} 107 /> 108 \<touchableopacity onpress={() => verifyotp()}> 109 \<view style={styles button}> 110 \<text style={styles button label}>{'verify'}\</text> 111 \</view> 112 \</touchableopacity> 113 \<touchableopacity onpress={() => requestotp()}> 114 \<view style={styles button}> 115 \<text style={styles button label}>{'resend token'}\</text> 116 \</view> 117 \</touchableopacity> 118 \</view> 119 )} 120 \</view> 121 ); 122 }; dê uma olhada mais de perto nas \<font color="#2166ae">requestotp\</font> e \<font color="#2166ae">verifyotp\</font> funções, que são responsáveis por chamar as respectivas funções do cloud code e validar suas respostas mais detalhes sobre como elas funcionam podem ser inspecionados nos comentários do código após criar a nova tela, importe e declare a em seu \<font color="#2166ae">app js\</font> (ou \<font color="#2166ae">app tsx\</font> ) depois disso, adicione um novo botão em seu \<font color="#2166ae">userlogin js\</font> (ou \<font color="#2166ae">userlogin tsx\</font> ) arquivo, permitindo que seu usuário navegue para a tela otp 4 testando o novo recurso otp vamos agora testar nossas alterações no aplicativo primeiro, registre um novo usuário contendo um email e um número de telefone válidos certifique se de usar a notação internacional (formato e 164) no número de telefone (por exemplo +14155552671) agora, navegue até a tela de otp a partir da tela de login e informe o mesmo email ou número de telefone de antes clique no botão de solicitação e você deve receber uma mensagem como esta, alterando a entrada ativa na sua tela se você informou um endereço de email, deve receber um email contendo o token otp; se um número de telefone foi passado, você receberá uma mensagem de texto sms no seu celular o email deve conter uma mensagem como esta, dependendo de como você configurou o modelo do sendgrid informe o token otp e clique em verificar se tudo correr bem, você deve estar agora na tela inicial com a seguinte mensagem conclusão ao final deste guia, você aprendeu como usar funções de código de nuvem do parse para integrar serviços de terceiros em sua aplicação react native no próximo guia, você aprenderá como trabalhar com usuários no parse