AccessToken과 RefreshToken은 웹 애플리케이션에서 사용자 인증과 권한 부여를 관리하는 데 사용되는 개념이다.
- 사용자가 로그인하면 서버에서 발급되는 인증 토큰이다.
- 일반적으로 짧은 수명을 가지며, 보안 상의 이유로 주기적으로 갱신되어야 한다.
- 클라이언트가 서버에 요청할 때마다 AccessToken을 함께 전송하며, 서버는 이를 검증하여 사용자의 신원과 권한을 확인한다.
- 주로 API 엔드포인트에 접근할 때 사용되며, 사용자의 인증 상태를 유지하는 역할을 한다.
- AccessToken의 유효기간이 만료되었을 때, 새로운 AccessToken을 발급받기 위해 사용되는 토큰이다.
- 보통 AccessToken보다 더 긴 수명을 가지며, 일반적으로 사용자가 로그아웃하거나 장시간 활동이 없을 때 만료될 수 있다.
- RefreshToken이 유효한 동안에는 새로운 AccessToken을 발급받을 수 있으므로, 사용자가 로그인 상태를 유지하며 끊김 없이 애플리케이션을 사용할 수 있도록 한다.
- 주로 보안을 강화하기 위해 RefreshToken은 안전한 저장소에 저장되어야 한다.
- 이러한 토큰 기반의 인증 시스템을 사용하면 보다 안전하고 효율적인 사용자 인증을 구현할 수 있다.
최초 로그인시 백에서 작성한 로직에 의해 AccessToken과 RefreshToken을 Respone값으로 받을 것이다.
Respone값으로 받아 온 AccessToken과 RefreshToken을 사용자는 항상 가지고 있다가 서버에게 어떠한 요청할 때 사용해야 한다.
AccessToken이 만료되었을 경우 UX적 불편함 없이 AccessToken을 재발급 받을 수 있어야 한다.
(저는 보통 Redux로 상태관리를 했기때문에 Redux에 유저 정보를 저장했습니다.)
아래는 제가 작성한 AccessToken 재발급 코드입니다.
하나의 코드로 작성하려 했지만 AccessToken과 RefreshToken과 같은 사용자정보를 Redux를 통해 관리를 하고 있었기 떄문에 2개의 컴포넌트로 나누어서 작성을 했습니다.
import axios from "axios";
const api = axios.create({
baseURL: "본인이 사용 할 baseURL주소",
});
const getNewAccessToken = async (RefreshToken) => {
try {
const response = await axios.post(
"본인이 사용 할 요청주소",
{},
{
headers: {
Authorization: `Bearer ${RefreshToken}`,
},
},
);
const newAccessToken = response.data.token;
return newAccessToken;
} catch (error) {
throw error;
}
};
export { api, getNewAccessToken };
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { api, getNewAccessToken } from "./api";
import { setUser } from "../slice/UserSlice";
import { persistor } from "../store";
const Token = () => {
const user = useSelector((state) => state.users);
const dispatch = useDispatch();
useEffect(() => {
// 로그인 상태인 경우에만 인터셉터 설정
if (user?.AccessToken) {
// 로컬 스토리지에서 사용자 데이터 가져오기
const storedData = localStorage.getItem("persist:root");
if (storedData) {
// 사용자 데이터에서 AccessToken과 RefreshToken 추출
const parsedData = JSON.parse(storedData);
const usersData = JSON.parse(parsedData.users);
const AccessToken = usersData.AccessToken;
const RefreshToken = usersData.RefreshToken;
// Request Interceptor 추가
// interceptors.request.use()를 사용하여 모든 요청 전송 전에 실행되는 함수이다.
api.interceptors.request.use(
async (config) => {
// AccessToken이 있으면 요청에 헤더에 포함하여 보냄
if (AccessToken) {
config.headers.Authorization = `Bearer ${AccessToken}`;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
// Response Interceptor 추가
// interceptors.response.use()는 모든 응답 수신 후에 실행되는 함수이다.
api.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
// 응답이 에러인 경우 처리
console.log(error);
if (
error.response &&
(error.response.status === 500 || error.response.status === 401)
) {
// 만료된 AccessToken으로 인증 실패한 경우
// persistor.purge(); // 영구 저장된 모든 상태를 초기화
try {
// 새로운 AccessToken을 받아와서 다시 요청을 보냄
const newAccessToken = await getNewAccessToken(RefreshToken);
// 사용자 정보와 새로운 AccessToken을 업데이트
dispatch(
setUser({
id: user.id,
nickname: user.nickname,
AccessToken: newAccessToken,
RefreshToken: user.RefreshToken,
}),
);
persistor.flush(); // 상태를 영구적으로 저장
error.config.headers.Authorization = `Bearer ${newAccessToken}`;
window.location.reload(); // 페이지 재시작
return api.request(error.config);
} catch (refreshError) {
// RefreshToken으로 AccessToken 재발급에 실패한 경우
console.error("Failed to get new access token:", refreshError);
throw error;
}
}
return Promise.reject(error);
},
);
} else {
console.log("No data found in localStorage.");
}
}
}, [user?.AccessToken, dispatch]);
};
export default Token;
위 코드는 AccessToken이 만료되었을때 request와 response를 가로채서 재발급해주고 요청된 request를 재요청해주는 코드이다.
(보기에 엉망진창일수 있지만 양해바랍니다...)
이렇게 AccessToken 만료시 request와 respones를 가로채서 사용자가 모르게 재발급을 해준다면 UX적인 문제없이 AccessToken갱신이 가능할 것이다.
AccessToken과 RefreshToken을 왜 사용하는지, 어떻게 언제 사용하는지 이해한다면 어렵지 않게 구현할 수 있을 것이다.
정리하면서 다시보니 RefreshToken 만료시에 대해서는 따로 조치를 한게 없네요... 열심히 수정해서 테스트 후 문제없으면 글에 추가하겠습니다!.
설명도 이상하고 코드도 이상한 글임에도 읽어주셔서 감사합니다. : )
좋은 글 잘 보고 갑니다 :)