[프로젝트] 클라이언트에서 Token 관리, contexts

fejigu·2023년 5월 2일
1

React Native Project

목록 보기
8/17
post-thumbnail



📍Aws Cognito를 활용한 회원가입 프로세스

🔎 continue 버튼을 누르는 순간

//signUp 함수가 실행 
<TouchableOpacity style={styles.continuebtn} onPress={signUp} >
        <Text style={styles.continuebtnfont}>Continue</Text>
</TouchableOpacity>

🔎 사용자 입장

→ 이메일 주소와 패스워드 입력 후, continue 버튼을 누른다.
→ 전화번호 인증 페이지로 이동한다.
//사용자 입장에서는 아직 회원가입 과정(진행중)

🔎 개발 프로세스

continue 버튼이 눌린 순간, signUp 함수 실행
→ 이메일 주소와 패스워드로 Aws Cognito에 Userpool 생성(회원가입 된 것임)
→ Userpool이 생성되자마자, 로그인 요청
→ 로그인 시 받은 Token 보내기
→ Aws로부터 받은 저장된 상태 응답에 따라 스크린 이동하기 
→ 전화번호 인증을 하지 않았을 경우, 전화번호 인증 스크린으로 이동(!data.data.phone_verified)
→ 전화번호 인증을 하고 그 외 정보를 입력하지 않았을 경우, 유저 정보 입력 스크린으로 이동(!data.data.userData_verified)
→ 모든 정보를 입력한 경우, 홈 스크린으로 이동

👉🏻 사용자 입장에서는 아직 회원가입 진행 중이지만, 개발 단에서는 이메일과 패스워드를 입력하고 continue 버튼을 누르는 순간 Aws Cognito에 Userpool을 생성하고, 바로 로그인 요청을 하고 로그인 시 받은 토큰을 백엔드에 보내고 이후 정보들을 저장해야한다.

Aws Cognito를 사용한 회원가입 프로세스가 처음이었기에 이 프로세스를 이해하고 정리하는데 시간이 다소 걸렸다.




🔥 로그인 시 토큰 관리 이슈

👉🏻 처음에 Aws Cognito를 통한 로그인 과정에서 받은 Token을 리턴하고 호출을 잘못 하여, 백엔드에 토큰과 함께 전화번호 인증번호를 요청하였더니 유효하지 않다는 응답을 받았다.

idToken과 accessToken은 객체 형태인데, 객체 형태로 리턴하지 않았으며 각각을 따로 호출하여 idToken은 새로 발급받은 토큰으로, accessToken은 이전에 발급받은 토큰. 이런식으로 올바르지 않은 조합으로 백엔드에 보내 유효하지 않은 토큰이라는 응답을 받은 것이다.

이후 아래와 같이 수정하였더니, 올바르게 인증코드가 발송되었고 인증번호 확인까지 올바르게 되었다.

//실제 적용 코드 
import * as React from 'react';
import { useContext } from 'react';
import { StyleSheet, Text, View, TouchableOpacity, TextInput } from "react-native";
import { SignUpContext } from '../contexts/SignUpContext';
import { AuthContext } from '../contexts/AuthContext';
import { Amplify, Auth } from 'aws-amplify'
import CryptoJS from 'crypto-js';



interface EmailSignInScreenProps {
  navigation: any;
  accessToken: any;
}

const CLIENT_ID = process.env.REACT_APP_CLIENT_ID || '';
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET || '';

Amplify.configure({
    Auth: {
      region: '리전 입력',
      userPoolId: '유저풀아이디 입력',
      userPoolWebClientId: '유저풀웹클라이어트아이디 입력',
      identityPoolId: '아이덴티티풀아이디 입력'
    }
  });
  
  //해시
  function createSecretHash(username: string, clientId: string, clientSecret: string) {
    const hash = CryptoJS.HmacSHA256(username + clientId, clientSecret);
    return hash.toString(CryptoJS.enc.Base64);
  }
  
  interface CognitoUser {
    idToken: any;
  }
  
  const EmailSignInScreen: React.FC<EmailSignInScreenProps> = ({ navigation }) => {
    const { email, setEmail, password, setPassword } = React.useContext(SignUpContext);
    const { setIdToken, setAccessToken } = useContext(AuthContext); //
   
    //로그인 요청
    const signIn = async () => {
        try {
          const user = await Auth.signIn(email, password);
          console.log('User signed in successfully!', user);
          const cognitoToken = user.signInUserSession
          const {idToken: {jwtToken: idToken}, accessToken: {jwtToken: accessToken}} = cognitoToken
          //이 부분에서 처음에 idToken과 accessToken을 객체로 내보내지 않아 에러가 생겼었다. 
          return {idToken, accessToken}
        } catch (error) {
          console.error('Error signing in:', error);
          return {idToken: null, accessToken: null}
        }
      };
    
      //회원가입 요청
      const signUp = async () => {
        try {
          const secretHash = createSecretHash(email, CLIENT_ID, CLIENT_SECRET);
          const user = await Auth.signUp({
            username: email,
            password: password,
            clientMetadata: { SECRET_HASH: secretHash },
            attributes: { email: email },
          });
          console.log('User signed up successfully!', user);

      //백엔드에 토큰 보내기 
      //이 부분에서 idToken과 accessToken 각각 signIn()에서 호출해서 에러가 생겼었음.
      const {idToken, accessToken} = await signIn();
      console.log('백엔드에 보내는 idToken, accessToken 1', idToken, accessToken);
        if (idToken && accessToken) {
          setIdToken(idToken);
          setAccessToken(accessToken)
          console.log('백엔드에 보내는 idToken, accessToken 2', idToken, accessToken);
          const response = await fetch('https://us15zwfe54.execute-api.us-west-1.amazonaws.com/Prod/user/signin', {
            method: 'GET',
            headers: {
              Authorization: idToken,
            },
          });
            const data = await response.json();
            console.log('백엔드에 토큰 보내기 이후 Server response!', data);

        //받은 aws에 저장된 상태 응답에 따라 스크린 이동하기 
        //핸드폰 로그인을 하지 않을 경우
          if (!data.data.phone_verified) {
            navigation.navigate('PhoneNumberSignIn',);
            console.log('phone_verified!', data.data.phone_verified);
          } 
          //이후 정보를 입력하지 않은 경우
          else if (!data.data.userData_verified) {
              navigation.navigate('InfoSignIn');
              console.log('userData_verified!', data.data.userData_verified);
          } 
          //모든 정보를 입력한 경우
          else {
            navigation.navigate('Home');
            console.log('HomeScreen!', data.data.userData_verified);
          }
          }
        } catch (error) {
          console.error('Error signing up:', error);
        }
      };
      

  return (
   ...
      <TouchableOpacity style={styles.continuebtn} onPress={signUp} >
        <Text style={styles.continuebtnfont}>Continue</Text>
      </TouchableOpacity>
    </View>
  );
};



📍Token은 AuthContext로 관리

→ 회원가입 과정에서 첫 스크린에서 받은 토큰을 가지고, 스크린이 이동할 때마다 요청을 보내야하고, 각각의 스크린에서 사용자에게 입력받은 정보를 클라이언트에서 저장하고 있다가 회원가입 과정 마지막 페이지에서 한꺼번에 백엔드에 보내야했다.

그래서 이 때, TokenAuthContext로 사용자가 스크린 마다 입력한 정보는 SignUpContext로 관리하였다.

토큰 관리의 중요성을 더욱 느끼고 있는데, 아직 어려움을 겪고 있어 조금 더 학습이 필요한 것 같다.

import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, TextComponent } from "react-native";
import { TextInput } from 'react-native-gesture-handler';
import { SignUpContext } from '../contexts/SignUpContext';
import { AuthContext } from '../contexts/AuthContext';


interface PhoneNumberSignInScreenProps {
    navigation: any;
}

const PhoneNumberSignInScreen: React.FC<PhoneNumberSignInScreenProps> = ({ navigation }) => {
    const { phoneNumber, setPhoneNumber, codeNumber, setCodeNumber } = React.useContext(SignUpContext);
    const { idToken, accessToken } = React.useContext(AuthContext);

    //인증번호 요청
    const getCode = async () => {
        try {
          const response = await fetch('https://aws 주소', {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${idToken}`,
            },
            body: JSON.stringify({
              phoneNumber: phoneNumber,
              accessToken: accessToken,
            }),
          });
          const data = await response.json();
      
          if (response.status === 401) { 
            console.error('Server response:', 'getCode 401', data);
          } else if(response.status === 503){
            console.error('Server response:', 'getCode 503',data);
          }
          else {
            console.log('나머지 Server response:', data);
          }
        } catch (error) {
          console.error('Server response:', 'getCode error!', error);
        }
      };
      

    //인증번호 확인 요청 
    const confirmCode = async () => {
        try {
          const response = await fetch('https://aws주소', {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${idToken}`,
            },
            body: JSON.stringify({
              code: codeNumber,
              accessToken: accessToken,
            }),
          });
          const data = await response.json();
          if(response.status === 401) {
          console.log('Server response:', 'confirmCode !', data);
          } else {
            console.log('Server response:', data);
          }
        }
        catch (error) {
          console.error('Server response:', 'confirmCode error!', error);
        }
      };
      
    
    //휴대폰 번호 인증 후 회원정보 입력 상태에 따라 스크린 이동(info, home)
    const confirmNumberSignIn = () => {
        try {
          //미작성
            console.log('Server response:', 'confirmNumberSignIn ok!');
          } catch (error) {
           //미작성
            console.error('Server response:', 'confirmNumberSignIn error!');
          }
    }

    return (
        ...
      <TouchableOpacity style={styles.code} onPress={getCode}>
                <Text>Get the code</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.codenumber}>
                <TextInput placeholder='Enter the Code.' value={codeNumber} onChangeText={setCodeNumber}/>
            </TouchableOpacity>
            <TouchableOpacity style={styles.check} onPress={confirmCode}>
                <Text>Check</Text>
            </TouchableOpacity>
      ...
    );
};
profile
console.log(frontendjigu( ☕️, 📱); // true

0개의 댓글