React Native
...
Cloud Functions
클라우드 코드로 React Native에 OTP 로그인 기능 구현
11 분
클라우드 기능을 사용한 react native의 otp 인증 소개 이 가이드에서는 parse cloud code 기능을 사용하여 react native 앱에 twilio verify api를 통합하여 otp 로그인 기능을 활성화하는 방법을 배웁니다 cloud code 기능은 민감한 데이터를 처리하고 애플리케이션 외부의 다른 api를 사용할 수 있게 해주기 때문에 이러한 작업에 적합한 장소입니다 이 가이드에서는 otp 통합을 사용하여 react native 구성 요소를 구현하는 방법도 확인하여 이전 가이드의 사용자 이메일 확인 예제를 개선합니다 언제든지 이 튜토리얼로 구축된 전체 android 프로젝트에 접근할 수 있습니다 우리의 github 저장소에서 확인하세요 코틀린 예제 저장소 자바 예제 저장소 전제 조건 이 튜토리얼을 완료하려면 다음이 필요합니다 react native 앱이 생성되고 back4app에 연결됨 다음 방법을 이해하십시오 클라우드 함수를 배포하는 back4app에서 twilio에서 활성 계정이 필요합니다 무료 체험 계정을 생성할 수 있습니다 여기 sendgrid에서 활성 계정이 필요합니다 무료 체험 계정을 생성할 수 있습니다 여기 목표 parse cloud code 함수를 사용하여 twilio verify api를 통합하고 react native 앱에서 사용합니다 1 twilio verify 설정 twilio는 현재 다양한 회사에서 사용되는 잘 알려진 2fa 토큰 및 otp 서비스 제공업체입니다 verify api는 개발자가 전화, 이메일을 확인하고 번거로움 없이 비밀번호 없는 로그인을 수행할 수 있도록 돕기 위해 간소화된 api 세트입니다 이 가이드에서는 sms 및 이메일 확인 방법을 사용하여 애플리케이션에 otp 기능을 활성화합니다 공식 verify 문서 https //www twilio com/docs/verify/api , 따라서 어떤 단계가 혼란스러워지면 참조하십시오 twilio 및 sendgrid 계정을 생성한 후(자동화된 이메일에 사용자 토큰을 포함하여 보내기 위해 sendgrid가 필요합니다), 각 서비스 대시보드에서 검색할 수 있는 다음 식별자를 적어두십시오 twilio의 계정 sid 및 인증 토큰; sendgrid id 및 간단한 이메일 템플릿 id 이제 twilio verify에서 새로운 verify 서비스를 생성하십시오 대시보드 https //www twilio com/console/verify/dashboard , 이는 otp 요청 및 검증을 관리할 구성 세트입니다 여기에서 서비스 sid를 적어두십시오 원하신다면 verify 서비스 내에서 새로운 이메일 통합을 생성하여 sendgrid 키를 추가하여 이메일 코드 전송을 활성화할 수도 있습니다 대시보드 https //www twilio com/docs/verify/email 그게 전부입니다 모든 설정이 완료되었으며 이제 cloud code 함수를 생성할 수 있습니다 2 cloud code functions를 통한 통합 앞서 논의한 바와 같이, 애플리케이션에서 cloud code 함수를 사용하면 코드의 유연성이 크게 향상되어 재사용 가능한 메서드를 앱에서 분리하고 그 동작을 더 잘 제어할 수 있습니다 우리의 cloud functions 시작 가이드 https //www back4app com/docs/get started/cloud functions 를 확인하거나 검토할 수 있습니다 첫 번째 클라우드 함수를 생성해 보겠습니다 함수 이름은 requestotp requestotp , 이 함수에서는 otp가 생성되어 사용자가 선택한 방법으로 전송됩니다 이 함수는 두 개의 매개변수를 받아야 합니다 userdata userdata 는 이메일 또는 전화번호를 포함하고, verificationtype verificationtype , 어떤 인증 방법을 사용할지를 지정합니다 email email 또는 sms sms 여기 함수 코드가 있습니다 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 }); 상단에서 twilio api 키를 정의하고 헬퍼 라이브러리를 가져왔음을 주의하세요 back4app의 parse server 구현은 기본적으로 twilio 라이브러리에 대한 접근을 제공하므로, 서버에 설치할 필요가 없습니다 두 번째이자 마지막 클라우드 함수는 verifyotp verifyotp , 이 함수는 사용자의 토큰을 twilio의 verify에서 검증하고 자동으로 로그인하여 세션을 생성하고 그 id를 애플리케이션으로 반환합니다 따라서 비밀번호 없이 로그인할 수 있습니다 네 개의 필수 매개변수가 있으며, 처음 두 개는 이전 함수와 동일하고, usertoken usertoken , 제공된 인증 토큰을 포함하고, 또한 userinstallationid userinstallationid , 이는 나중에 설명될 것입니다 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 }); 다음 단계로 이동하기 전에 이러한 기능을 parse 서버에 배포해야 합니다 3 react native에서 otp 기능 만들기 이제 사용자 이메일 확인의 동일한 프로젝트 예제를 기반으로 하여 새로운 otp 기능을 활성화하는 몇 가지 변경 사항을 추가해 보겠습니다 가이드를 계속 진행하기 전에 프로젝트 예제를 다운로드하고 설정하는 것을 권장합니다 먼저, 사용자가 전화번호를 사용하여 로그인할 수 있도록 새로운 입력 필드를 추가하고 그 값을 userregistration js userregistration js (또는 userregistration tsx userregistration tsx ) 파일의 사용자 등록 저장 방법에 추가합니다 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 }; 이제 새로운 userotp userotp 화면을 생성하여 모든 otp 프로세스를 처리하겠습니다 이 화면에는 두 개의 입력 필드가 있으며, 첫 번째 필드는 사용자가 otp를 받을 수단(이메일 주소 또는 전화번호)을 제공하는 것입니다 다른 입력 필드는 otp 요청을 제출하기 전에 숨겨져 있으며, 사용자가 받은 토큰을 포함합니다 전체 userotp js userotp js (또는 userotp tsx userotp tsx ) 코드입니다 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 }; 자세히 살펴보세요 requestotp requestotp 및 verifyotp verifyotp 함수는 각각의 클라우드 코드 함수를 호출하고 그 응답을 검증하는 역할을 합니다 그들이 어떻게 작동하는지에 대한 더 많은 세부정보는 코드 주석에서 확인할 수 있습니다 새 화면을 만든 후, app js app js 에 가져오고 선언하세요 (또는 app tsx app tsx ) 그 후, userlogin js userlogin js (또는 userlogin tsx userlogin tsx ) 파일에 새 버튼을 추가하여 사용자가 otp 화면으로 이동할 수 있도록 합니다 4 새로운 otp 기능 테스트 이제 앱의 변경 사항을 테스트해 보겠습니다 먼저, 유효한 이메일과 전화번호를 포함한 새 사용자를 등록하세요 전화번호에는 국제 표기법(e 164) 형식(예 +14155552671)을 사용해야 합니다 이제 로그인 화면에서 otp 화면으로 이동하고 이전과 동일한 이메일 또는 전화번호를 입력하세요 요청 버튼을 클릭하면 화면에서 활성 입력이 변경되는 메시지를 받아야 합니다 이메일 주소를 입력했다면 otp 토큰이 포함된 이메일을 받아야 하고, 전화번호를 입력했다면 모바일 전화로 sms 문자 메시지를 받게 됩니다 이메일에는 sendgrid 템플릿 설정에 따라 다음과 같은 메시지가 포함되어야 합니다 otp 토큰을 입력하고 확인을 클릭하세요 모든 것이 잘 진행되었다면 이제 다음 메시지와 함께 홈 화면에 있어야 합니다 결론 이 가이드의 끝에서, react native 애플리케이션에 타사 서비스를 통합하기 위해 parse cloud code 함수를 사용하는 방법을 배웠습니다 다음 가이드에서는 parse에서 사용자와 작업하는 방법을 배울 것입니다