React에서 유저 상태 관리하기

Sinf·2022년 7월 6일
2

고민의 흔적

목록 보기
32/38
post-thumbnail

Nest.js에서 JWT를 활용해 유저를 인증했다.
이제, 클라이언트에서 유저 정보, 유저 인증을 구현해보자.

일단, 토큰을 통해 유저가 로그인 중이라고 판단하기로 했다.

어떻게 구현해야 할까?

구현 방식을 생각해본다.

  1. 쿠키로 전달 받은 토큰을 저장한다.
  2. 토큰을 전역에서 사용할 수 있도록 전역 상태 라이브러리를 사용하고, 토큰을 저장한다.
  3. 접속 시 로그인 여부 확인할 함수 정의 (쿠키나 저장소를 확인해 토큰이 있다면 로그인 상태 유지)

쿠키로 전달받은 토큰 저장

먼저, 토큰을 어디에 저장해야 할까?

로컬 스토리지? 쿠키?

인터넷을 찾아보니 쿠키 상태로만 토큰을 저장하거나 로컬 스토리지에 토큰을 저장하거나 보안과 관련된 질문과 글들이 많았다.

나는 로컬 스토리지를 사용하는 것으로 결정했다.
일단, MDN에서 정보 저장에 대해서 쿠키보다 Modern Storage를 사용할 것을 추천한다.

구글 로그인을 사용하지 않았다면, 로그인 요청 후 응답을 통해 발급 받은 토큰을 바로 로컬 스토리지로 저장하겠지만, 구글 로그인 성공 후 리다이렉션 되기 때문에 함수를 정의해 App 컴포넌트가 호출될 때 저장하도록 구현했다.

const savedToken = localStorage.getItem('access-token');
// 쿠키 파싱 후 토큰
const cookieToken = parseCookie()['access-token']

if (!savedToken && !!cookieToken) {
  localStorage.setItem('access-token', cookieToken);
}

쿠키를 파싱해 토큰을 가져오는 로직은 다음과 같다.

function parseCookie() {
  const { cookie } = document;

  if (!cookie) {
    return;
  }

  return Object.formEntries(
    cookie
      .split(';')
      .map((x) => x.split('='))
      .map(([key, value]) => [
        decodeURIComponent(key),
        decodeURIComponent(value),
      ])
    );
}

key:value 형태로 쿠키를 파싱하고, key를 통해 토큰을 가져온다.

전역 상태 관리 라이브러리로 토큰 관리하기

전역 상태 관리

토큰과 같이 모든 페이지에서 사용하는 상태와 같은 경우,
전역 상태 관리 라이브러리를 통해 관리한다.

React에서 사용할 수 있는 라이브러리는
Redux, Recoil, Zustand, Jotai와 같이 다양하다.

나는 zustand를 선택했다.
적은 보일러 플레이트를 가지고, Typescript를 사용하기 좋다.

설치 후 사용하기

npm i zustand # yarn add zustand

설치 후 Store 타입을 정의한다.

interface Store {
  token?: string;
}

그리고 정의된 타입으로 store를 구현한다.

import create from 'zustand';

const store = create<Store>((set) => ({}))

create 함수에 파라미터로 함수가 전달된다.
전달되는 함수의 파라미터는 set (setter와 같은 역할)
리턴되는 값은 객체형태를 갖는데, 해당 객체에 상태를 저장한다.

토큰 관리

이제 로컬 스토리지의 토큰을 전역 상태로 저장한다.

const store = create<Store>((set) => ({
  token: localStorage.getItem('access-token') || parseCookie()['access-token']
}))

로컬 스토리지에 저장된 토큰을 가져오거나, 쿠키에 저장되어 있는 토큰을 가져온다.

로그인 상태 유지하기

이제, 로컬 스토리지에 토큰도 저장했고, 전역 상태에서 토큰도 관리하기 시작했다.

이런 상황에서 앱을 종료했다가 다시 접속한다면 로그인이 유지되고 있을까? (토큰이 만료되지 않았다는 가정하에)

아쉽게도 그렇지 않다.

구글 로그인 후 홈으로 리다이렉션 되는데, 이 때도 로그인 중이라고 나오지 않는다...

왜 로그인이 안된 상태로 보일까?

1. 쿠키에 토큰이 없는가?

쿠키는 있다. 받아왔다.

2. 로컬 스토리지에 저장되지 않았다?

로컬 스토리지에 토큰은 저장되어 있다.

3. 상태에 저장되지 않았다?

다음의 경우에 콘솔에 로그를 찍었다.

  1. Navigation 컴포넌트에서
  2. Navigation 컴포넌트의 useEffect
  3. main.tsx

main - Navigation - Navigation useEffect 순으로 token이 있는 것으로 콘솔에 찍혔다.

상태에는 저장되어 있다.

4. 다른 서비스를 참고해보자.

쉽게 이유를 알 수 없어서 다른 서비스 구현이 어떻게 되어 있는지 확인해봤다.

Coderland를 확인해보면, 전역 상태에 초기화 함수를 정의하고 App이 렌더링 되기 전에 초기화 함수를 호출한다.

해당 초기화 함수는 코더랜드 useAuthStore에 정의되어 있다.

초기화 함수에서는 토큰을 통해 유저 정보를 조회한다.
조회된 유저 정보를 통해, store의 user 정보를 갱신한다.

아?

추측해보자면, 새로고침이나 리다이렉션과 같이 웹 앱이 다시 그려지는 경우 전역 상태를 관리하는 store에 저장된 값이 초기화된다.

그래서 초기화 함수를 통해 다시 토큰 값을 로컬 스토리지로부터 가져와야 한다. (로컬 스토리지에 없다면 쿠키로부터) 초기화 과정이 있어야 전역 상태가 관리되고 App에서 어떤 페이지를 가도 로그인 상태가 유지된다.

그래서 App 컴포넌트를 렌더링하기 전 main에서 초기화 함수를 실행하고 App을 렌더링한다.

결론

조금 복잡하게 진행되었지만 과정을 살펴보면

  1. 쿠키로 전달받은 토큰을 로컬 스토리지에 저장한다.
  2. 모든 페이지에서 토큰을 유지하기 위해 전역 상태 관리 라이브러리를 사용한다. 그리고 초기화 함수를 통해 새로고침이나 리다이렉션 되는 상황에서도 Store의 값이 유지될 수 있도록 한다.
  3. 컴포넌트에서 token을 사용해 인증이 필요한 API에 접근한다.

앱이 새로 렌더링되는 상황에서 store의 값을 넣어주는 함수가 필요하다는 것.

좋은 참고자료

profile
주니어 개발자입니다. 🚀

0개의 댓글