자동 로그인 연장 / 자동 로그아웃 구현하기
겪은 문제
클라이언트에서 로컬스토리지의 액세스 토큰 유무로 로그인/비로그인을 판단하고 있었습니다. 그런데 액세스 토큰이 만료됐음에도 계속 로컬스토리지에 유효하지 않는 액세스 토큰이 남아있었습니다.
따라서 유저는 로그인 된 상태의 UI를 보고있으나, 정작 API를 요청하는 순간에는 로그인이 만료됐다는 에러 모달을 받게 됩니다.
해결 방안
- 액세스 토큰이나 리프레시 토큰 중 하나만 만료 됐다면 알아서 재발급 후에 새로운 토큰을 로컬스토리지에 넣어주는 로직이 필요했습니다. (자동 로그인 연장 구현)
- 액세스 토큰, 리프레시 토큰이 둘 다 만료 됐다면 자동으로 로컬스토리지의 값들을 비워 비로그인 상태로 만들어주는 로직도 필요했습니다. (자동 로그아웃 구현)
해결
기존 코드
-
다른 로직은 없이 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;
});