<dev.log/>;

자동 로그인 연장 / 자동 로그아웃 구현하기

Axios 공식 문서

겪은 문제

클라이언트에서 로컬스토리지의 액세스 토큰 유무로 로그인/비로그인을 판단하고 있었습니다. 그런데 액세스 토큰이 만료됐음에도 계속 로컬스토리지에 유효하지 않는 액세스 토큰이 남아있었습니다.

따라서 유저는 로그인 된 상태의 UI를 보고있으나, 정작 API를 요청하는 순간에는 로그인이 만료됐다는 에러 모달을 받게 됩니다.

해결 방안

  1. 액세스 토큰이나 리프레시 토큰 중 하나만 만료 됐다면 알아서 재발급 후에 새로운 토큰을 로컬스토리지에 넣어주는 로직이 필요했습니다. (자동 로그인 연장 구현)
  2. 액세스 토큰, 리프레시 토큰이 둘 다 만료 됐다면 자동으로 로컬스토리지의 값들을 비워 비로그인 상태로 만들어주는 로직도 필요했습니다. (자동 로그아웃 구현)

해결

기존 코드

  • 다른 로직은 없이 API 요청 시, 로그인 할때 로컬스토리지에 담아둔 액세스 토큰과 리프레시 토큰을 헤더에 담아서 보내고 있었습니다.

    //instance.js
    
    import axios from 'axios';
    
    const instance = axios.create({
      baseURL: process.env.REACT_APP_API,
      headers: {
        accessToken: `Bearer ${localStorage.getItem('accessToken')}`,
        refreshToken: `Bearer ${localStorage.getItem('refreshToken')}`,
      },
    });
    

추가한 코드

(서버가 해줄 일)

  • 클라이언트에서 보낸 헤더의 토큰 중 하나만 만료됐다면, 서버가 새로 발급 받은 토큰을 다시 보내주는 로직이 추가됐습니다.

(클라이언트에서 할 일)

  • API 요청시 새로운 토큰 값과 '토큰이 재발급 됐습니다' 라는 메세지가 응답 값으로 왔다면 interceptor가 작동하는 로직을 추가했습니다.

  • 그리고 유저가 요청한 API를 새로 받은 토큰 값을 헤더에 담아 자동으로 재요청하는 로직도 추가했습니다.

  • 토큰이 모두 만료되어 서버에서 401에러와 '토큰이 만료 됐습니다' 라는 메세지가 응답 값으로 왔다면 로컬스토리지의 값을 비워주는 로직을 추가하고 마무리 했습니다.

    //instance.js
    
    //응답 값을 인터셉터하고 로직을 실행합니다.
    instance.interceptors.response.use(
      (config) => {
        if (config.status === 201 && config.data.msg.includes('재발급')) {
          //헤더에 새로 받은 토큰 값을 넣어 자동으로 재요청을 보내는 로직을 return합니다
          return axios({
            ...config.config,
            headers: {
              accessToken: `Bearer ${config.data.accessToken}`,
              refreshToken: `Bearer ${config.data.refreshToken}`,
            },
          }).then(() => {
            //제대로 작동했다면 새로 받은 토큰 값들을 로컬스토리지에 새로 넣어줍니다.
            localStorage.setItem('accessToken', config.data.accessToken);
            localStorage.setItem('refreshToken', config.data.refreshToken);
          });
        } else {
          return config;
        }
      },
      (error) => {
        if (error.response.status === 401 && error.response.data.errMsg.includes('만료')) {
          //토큰이 모두 만료됐다는 응답 값을 받으면 로컬스토리지를 비워줍니다.
          localStorage.clear();
        }
        return Promise.reject(error);
      },
    );
    

새로운 문제

  • 재요청은 제대로 작동하지만, 이후에 API 요청에서는 다시 이전의 만료된 토큰 값을 헤더에 넣어 보내고 있었습니다. 하지만 로컬스토리에는 새로운 토큰 값이 잘 저장되어 있습니다.
  • instance에서 헤더에 담은 값이 최신화가 안되서 생기는 문제라고 판단하고 검색을 시작했습니다.
  • request도 인터셉터하여 매번 최신화된 토큰 값을 넣어주는 로직으로 해결했습니다.

추가한 코드

//instance.js

//서버에 요청이 가기 전 인터셉트 합니다.
instance.interceptors.request.use((config) => {
  config.headers.accessToken = `Bearer ${localStorage.getItem('accessToken')}`;
  config.headers.refreshToken = `Bearer ${localStorage.getItem('refreshToken')}`;

  return config;
});