웹에서 개발할 당시에는 세션 또는 로컬에 토큰을 저장하여 로그인 상태를 유지하게 했습니다.
하지만 앱에서는 세션과 로컬을 활용할 수 없기 때문에 AstncStorage를 활용하여 디바이스에 정보를 저장하여야 합니다.

AsyncStorage 설치

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

 

AsyncStorage에 데이터 저장하기

// Token.js

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

// AccessToken 저장
export const setAccessToken = async token => {
  await AsyncStorage.setItem('accessToken', JSON.stringify(token));
};

// refreshToken 저장
export const setRefreshToken = async token => {
  await AsyncStorage.setItem('refreshToken', JSON.stringify(token));
};

// 유저 정보 저장
export const setCurrentUser = async currentUser => {
  await AsyncStorage.setItem('currentUser', JSON.stringify(currentUser));
};

AsyncStorage를 불러와 setItem 메서드로 디바이스에 정보를 저장합니다. 이 때 첫 번째 인자에 저장할 정보의 key, 두 번째 인자에 저장할 데이터를 작성합니다. 

 

import axios from 'axios';
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit';
import {Alert} from 'react-native';

import api from '../api';
import {
  setAccessToken,
  setRefreshToken,
} from '../Token';

const login = createAsyncThunk('login', async (data, {rejectWithValue}) => {
  try {
    const res = await axios.post(api.login(), data, {});
    setAccessToken(res.data.accessToken);
    setRefreshToken(res.data.refreshToken);
    setCurrentUser(res.data.user);
    return res.data;
  } catch (err) {
    Alert.alert('로그인 정보', '이메일과 비밀번호를 확인해주세요.');
    return rejectWithValue(err.response.data);
  }
});

const initialState = {
  loginState: false,
	...
};


export const AccountsSlice = createSlice({
  name: 'accounts',
  initialState,
  reducers: {
  	...
  },
  extraReducers: {
    [login.fulfilled]: (state, action) => {
        state.loginState = true;
        state.currentUser = action.payload.user;
    },
 	...
  }
});

로그인 후 발급받은 access 토큰과 refresh 토큰을 AsyncStorage에 저장합니다. 진행한 프로젝트에서는 회원가입 후 바로 로그인 처리를 했으므로 회원가입에서도 같은 작업을 해줍니다. 로그인 상태를 바꾼 뒤 유저 정보도 AsyncStorage와 store에 저장해줍니다.

 

AsyncStorage에서 저장된 데이터 불러오기

앱을 새로고침 했을 때, state에 저장된 정보는 모두 삭제되어 있으므로 Asyncstorage에 저장된 정보를 바탕으로 로그인 여부를 확인합니다.

// Token.js

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

export const getAccessToken = async () => {
  const token = await AsyncStorage.getItem('accessToken');
  return token.replace('"', '').replace('"', '');
};

export const getRefreshToken = async () => {
  const token = await AsyncStorage.getItem('refreshToken');
  return token.replace('"', '').replace('"', '');
};

// {"exp": 50, "rank": "Y2", "shoeSize": 290, "upto": 2, "wingspan": 210, ...}
export const getCurrentUser = async () => {
  const info = await AsyncStorage.getItem('currentUser');
  const currentUser = JSON.parse(info);
  return currentUser;
};

다음과 같이 getItem메서드를 활용하여 'accessToken'과 'refreshToken' 그리고 'currentUser'에 저장된 토큰을 가져오는 함수를 작성합니다. 유저 정보는 객체형태이므로 저장된 문자열을 불러와 json로 변환해주어 사용합니다.

 

 저는 앱을 실행할 때 유저정보를 다시 state에 붙여주기 위해 앱 실행 시 async storage에 저장된 유저 정보를 확인하여 로그인 처리를 하였습니다. 하지만 함수를 불러와 이 부분에서 웹에서 작업할 때와 약간 다른점이 있었습니다.  AsyncStorage는 동기식이던 LocalStorage와 다르게 비동기 방식으로 동작하기 때문에 객체에 저장해서 사용하려면 undefined에러가 발생합니다.

 // 제대로 동작하지 않는 코드 ,,,
 
 useEffect(() => {
	...
    const user = getCurrentUser()
    if (user) {
    	dispatch(fetchCurrentUser(res));
    } else {
    	removeAccessToken();
        removeRefreshToken();
        removeCurrentUser();
    }
  }, [dispatch]);

따라서 다음과 같은 처리가 불가능하여 promise 객체와 .then을 활용하여 비동기 처리를 하였습니다.

  // 앱이 실행되고 처음 호출되는 함수에 적용
  
  useEffect(() => {
	...
    getCurrentUser().then(res => {
      if (res) {
        dispatch(fetchCurrentUser(res));
      } else {
        removeAccessToken();
        removeRefreshToken();
        removeCurrentUser();
      }
    });

  }, [dispatch]);

이렇게 하면 앱을 실행했을 때 유저 정보가 존재하면 로그인 처리를 하고 state에 유저정보를 저장할 수 있습니다.

 

AsyncStorage 데이터 삭제하기

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

// AccessToken 삭제
export const removeAccessToken = () => {
  AsyncStorage.removeItem('accessToken');
};

// RefreshToken 삭제
export const removeRefreshToken = () => {
  AsyncStorage.removeItem('refreshToken');
};

// 유저 정보 삭제
export const removeCurrentUser = () => {
  AsyncStorage.removeItem('currentUser');
};

 로컬이나 세션과 마찬가지로 removeItem 메서드를 활용하여 삭제하고자하는 데이터의 key를 인자로 넣어주면 됩니다.

회원관련 에러나 로그아웃 시 해당 함수들을 불러와 AsyncStorage를 비웠습니다.

import axiosTemp from '../axios';

const logout = createAsyncThunk('logout', async (arg, {rejectWithValue}) => {
  try {
    const res = await axiosTemp.post(api.logout(), {}, await getConfig());
    removeAccessToken();
    removeRefreshToken();
    removeCurrentUser();
    return res.data;
  } catch (err) {
    removeAccessToken();
    removeRefreshToken();
    removeCurrentUser();
    return rejectWithValue(err.response.data);
  }
});

로그아웃 시도 시 모든 정보를 지웠습니다.

여기에 사용된 axiosTemp는 axios 인스턴스로 response시 access토큰 만료 여부를 판단하고 refresh토큰으로 재발급 요청을 보내는 친구입니다. 이와 관련해서는 axios interceptor를 공부하시면 됩니다. 이 글에서는 중요하지 않으니 패스하겠습니다.

+ Recent posts