[React Native] 로그인 화면 구현 (백엔드) - 2. Firebase 로그인

2022. 6. 15. 01:27React Native/Intermediate

 

 


 


 

 


1. Firebase 로그인 연동

로그인도 회원가입과 동일하게 POST 방식을 쓰고 request payload, response payload 를 동일하게 가진다. 우선 firebase 사이트에서 sign in end point를 가져와 misc.js에 작성하자. 이전 회원가입에서 쓴 코드를 똑같이 복붙해주고 UP만 IN으로 바꿔주자.

 

// /app/utils/misc.js
export const APIKEY = `AIzaSyC-D7xBF_pDDyJZSb0DpC6W-FUJu_UToL8`;
export const SIGNUP = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${APIKEY}`;
export const SIGNIN = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${APIKEY}`;
// /app/store/actions/user_actions.js
import {SIGN_IN, SIGN_UP} from '../types';
import axios from 'axios';
import {SIGNUP, SIGNIN} from '../../utils/misc';
export function signIn(data) {
  const request = axios({
    method: 'POST',
    url: SIGNIN,
    data: {
      email: data.email,
      password: data.password,
      returnSecureToken: true,
    },
    header: {
      'Content-Type': 'application/json',
    },
  })
    .then(response => {
      console.log(response.data);
      return response.data;
    })
    .catch(err => {
      alert('에러 발생');
      return false;
    });
  return {
    type: SIGN_IN,
    payload: request,
  };
}
export function signUp(data) {
  const request = axios({
    method: 'POST',
    url: SIGNUP,
    data: {
      email: data.email,
      password: data.password,
      returnSecureToken: true,
    },
    header: {
      'Content-Type': 'application/json',
    },
  })
    .then(response => {
      console.log(response.data);
      return response.data;
    })
    .catch(err => {
      alert('에러 발생');
      return false;
    });
  return {
    type: SIGN_UP,
    payload: request,
  };
}
// /app/reducers/user_reducer.js
import {SIGN_IN, SIGN_UP} from '../types';

export default function (state = {}, action) {
  switch (action.type) {
    case SIGN_IN:
      return {
        ...state,
        auth: {
          userId: action.payload.localId || false,
          token: action.payload.idToken || false,
          refToken: action.payload.refreshToken || false,
        },
      };
    case SIGN_UP:
      return {
        ...state,
        auth: {
          userId: action.payload.localId || false,
          token: action.payload.idToken || false,
          refToken: action.payload.refreshToken || false,
        },
      };

    default:
      return state;
  }
}

 

그런데 디버거로 확인해보면 앱을 종료했다가 다시 들어가면 로그인 정보가 사라져 다시 로그인 해야하는 불상사가 발생한다. 이를 해결하기 위해서 로컬 스토리지에 정보를 저장해둬야 하는데 이걸 이제 해보자.

 


2. AsyncStorage를 이용한 Token 저장

로그인 상태를 유지하기 위해서는 로컬 스토리지에 토큰을 저장하면 된다. React Native의 경우 새로운 유형의 client storage인 AsyncStorage를 쓸 수 있고 이게 로컬 스토리지를 대신 할 수 있다. 

 

https://reactnative.dev/docs/asyncstorage

 

🚧 AsyncStorage · React Native

Deprecated. Use one of the community packages instead.

reactnative.dev

공식문서를 보면 AsyncStorage는 암호화되어 있지 않고 비동기적이고 영구적인 key-value 스토리지 시스템이라고 나와있다. 그런데 기존 AsyncStorage는 중단되었다.

 

https://reactnative.directory/?search=storage

 

https://reactnative.directory/?search=storage

 

reactnative.directory

 

위의 AsyncStorage를 사용할 건데 설치 방법은 아래 사이트에 나와있다.

https://react-native-async-storage.github.io/async-storage/docs/install/

 

Installation | Async Storage

Get library

react-native-async-storage.github.io

// npm
npm install @react-native-async-storage/async-storage

// yarn
yarn add @react-native-async-storage/async-storage

 

import AsyncStorage from '@react-native-async-storage/async-storage';

위 코드는 misc.js에 추가해주자.

 

그리고 공식문서에서 usage를 봐보면 storing data, reading data가 있다.

 

데이터를 저장할 때는 setItem()이라는 메소드를 사용하고 이는 Promise 객체를 반환하는 메소드이다. 전에 다룬 axios와 동일하게 비동기 작업 후 성공했을 때와 실패했을 때의 콜백 함수를 정의하게 된다. 하나의 스트링 값을 추가하는 경우가 있고 객체를 추가하는 경우가 있다.

 

데이터를 읽어올 때는 getItem()이라는 메소드를 사용하는데 setItem과 똑같이 Promise 객체를 반환하고 하나의 스트링과 객체를 읽어오는 경우가 있다.

 

그런데 이 방법이 아닌 다른 방법을 쓰도록 하자. Usage가 아닌 API를 들어가면 multiGet과 multiSet이 있다. 이것들은 하나의 스트링이나 객체가 아니라 여러 개의 [key, value] 값들이 배열로써 들어간다. 

 

authForm.js에 submit 부분에 콜백함수로 manageAccess 함수를 사용하자. 이 함수는 actioncreator의 콜백함수다. 따라서 actioncreator에 의해 action이 리턴되었을 것이다. 그 action과 기존의 state 값이 인자로 들어간 reduce 함수에 의해서 새로운 state 값이 업데이트 되었을 것이다. 이 업데이트된 값들은 mapstateprops와 connect를 통해서 react native component props에 저장되어 있을 것이다. 

즉, user_reducer.js에서 정의한 새로운 state 값인 auth 객체가 컴포넌트 props에 저장이 되어 있는 상태인 것이다. 그래서 props.User.auth.userId가 없다면 에러를 발생시켜주고 그렇지 않다면 multiSet 메소드를 호출하고 메인 화면에 이동하게끔 구현할 것이다.

setTokens의 인자로 this.props.User.auth 와 콜백함수를 넘겨주자. setState를 통해서 hasErrors 값을 조절하고, props.goWithoutLogin()을 통해 이동하도록 하자. 

그러면 setTokens라는 함수를 정의해야 하는데 이건 misc.js 에서 정의하자.

 

공식문서 상의 multiSet을 그대로 가져와서 함수를 수정하도록 하자. 우선 외부에서 사용할 것이므로 export 해주고 함수 이름을 setTokens로 바꿔준다. 이 메소드는 auth 객체를 받아왔는데 이걸 values로 받아오고 콜백함수는 callBack으로 받아오자. 로그인 상태를 유지하기 위해 저장해야하는 정보는 auth 에 있는 3개의 key 값이다. 이 Key 값들을 정의해주자. userId, token, refToken 이 3개의 pair를 배열로 multiSet 안에 넣어주면 된다. 그리고 MultiSet은 promise 객체를 반환하는 메소드였으므로 then 함수로 response를 받아 callBack 함수를 불러오자. 콜백함수가 호출되었다는 건 AsyncStorage에 데이터를 저장하는 데 문제가 없었다는 의미다. 

 

여기서 잠깐 async await 을 살펴보자. 이건 js 문법으로 함수를 선언할 때 async를 붙이게 되면 단 하나만을 분명히 언급해주는 것이다. 이 함수는 promise 객체를 반환한다는 것인데 이 async 를 따라오는 특별한 것이 await이다. await은 promise를 반환하는 함수가 어떤 특별한 결과를 낼 때까지 js 한테 다른 작업을 하지말고 기다리라는 명령을 주는 것이다. 그래서 await은 보통 함수에서는 쓸 수 없다.

개인 식별자를 저장하는 것은 굉장히 중요한 것이기 때문에 비동기로 처리하고 그 동안 다른 작업을 못하게 하는 것이다.

 

// /app/utils/misc.js
export const APIKEY = `AIzaSyC-D7xBF_pDDyJZSb0DpC6W-FUJu_UToL8`;
export const SIGNUP = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${APIKEY}`;
export const SIGNIN = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${APIKEY}`;
import AsyncStorage from '@react-native-async-storage/async-storage';

export const setTokens = async (values, callBack) => {
  const firstPair = ['@diary_app@userId', values.userId];
  const secondPair = ['@diary_app@token', values.token];
  const thirdPair = ['@diary_app@refToken', values.refToken];
  try {
    await AsyncStorage.multiSet([firstPair, secondPair, thirdPair]).this(
      response => {
        callBack();
      },
    );
  } catch (e) {
    //save error
  }

  console.log('Done.');
};

export const getTokens = async () => {
  let values;
  try {
    values = await AsyncStorage.multiGet([
      '@diary_app@userId',
      '@diary_app@token',
      '@diary_app@refToken',
    ]);
  } catch (e) {
    // read error
  }
  console.log('Get Tokens: ', values);

  // example console.log output:
  // [ ['@MyApp_user', 'myUserValue'], ['@MyApp_key', 'myKeyValue'] ]
};

 

// /app/components/auth/index.js

import React, {Component, useEffect} from 'react';
import {
  StyleSheet,
  Text,
  View,
  ActivityIndicator,
  ScrollView,
} from 'react-native';
import AuthLogo from './authLogo';
import AuthForm from './authForm';
import {getTokens, setTokens} from '../../utils/misc';
const AuthComponent = ({navigation}) => {
  // state = {
  //   loading: false,
  // };
  const [loading, setLoading] = React.useState(false);
  goWithoutLogin = () => {
    navigation.navigate('AppTabComponent');
  };

  useEffect(() => {
    getTokens();
  }, []);

  if (loading) {
    return (
      <View style={styles.loading}>
        <ActivityIndicator />
      </View>
    );
  } else {
    return (
      <ScrollView style={styles.container}>
        <View>
          <AuthLogo />
          <AuthForm goWithoutLogin={goWithoutLogin} />
        </View>
      </ScrollView>
    );
  }
};

const styles = StyleSheet.create({
  loading: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  container: {
    flex: 1,
    backgroundColor: '#7487C5',
    paddingTop: 130,
    paddingLeft: 50,
    paddingRight: 50,
  },
});

export default AuthComponent;

3. AsyncStorage를 이용한 Token 읽어오기

token은 앱이 실행될 때마다 불러와져야 할 것이다. 그래야 로그인 상태를 유지할 수 있기 때문이다. 그래서 /auth/index.js 에서 호출하는 메소드 componentDidMount() 안에서 호출하고 함수명은 getTokens()라고 하자. 이것도 마찬가지로 misc.js에서 정의하고 가져오자.

공식문서의 multiGet을 가져와서 수정하자.

 

작성한 코드가 정상적으로 작동하는지 확인하기 위해서 시뮬레이터로 확인해보자. 3가지를 확인할 것이다.

  1. 앱에 로그인 이력이 없는 경우, token을 비롯한 개인 식별자 값이 Null 일 것이다.
    로그인 이력을 없애기 위해 앱을 지웠다가 다시 실행해보자. 다음과 같이 모두 Null 값을 가진다.
  2. 앱에 로그인 이력이 있는 경우, 앱을 껐다 다시 키더라도 앱에 로그인 이력이 남아있을 것이다.
    firebase 프로젝트에 등록되어 있는 로그인 정보를 활용하여 로그인해보자.
  3.  

 


참고 자료

https://www.inflearn.com/course/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%A4%91%EA%B8%89/dashboard

 

iOS/Android 앱 개발을 위한 실전 React Native - Intermediate - 인프런 | 강의

React Native 기반 모바일 앱 개발을 위한 중급 강의입니다. 프론트엔드의 심화내용 학습 뿐만 아니라 Firebase 기반의 백엔드 내용까지 함께 배우면서, 서버 연동/ 로그인/ 데이터 송수신/ 공공API 활

www.inflearn.com