프로젝트를 진행하면서 access token이 만료되었을 때 refresh token을 사용하여 access token을 재발급 받아야 하는 상황이 생겼습니다. 그래서 token 재발급에 대해 생각해보고 방법에 대해 알아보겠습니다.
refresh token을 사용하여 만료된 access token을 갱신하는 방법
- 클라이언트에서 API 요청을 보낸 후에 서버가 access 만료 여부를 확인합니다.
- 만약 access token이 만료되었다면 401 response를 보내고, 응답받은 클라이언트는 이전에 발급받은 refresh token을 헤더에 담아 api 요청을 합니다.
- 서버에서는 이전과 동일하게 refresh token 만료 여부를 확인합니다.
- 만료가 되지 않은 경우에 access token을 재발급해서 클라이언트에게 전달하고 클라이언트는 발급받은 access token을 Header에 담아 이전에 보내던 API를 재 요청합니다.
axios interceptors
axios intercepter를 사용하면 then 또는 catch로 처리되기 전에 요청과 응답을 가로채어 추가적인 작업을 수행할 수 있습니다.
axios interceptors request
interceptors request는 api를 요청하기 전에 가로채어 작업할 수 있습니다.
클라이언트에서 JWT 토큰을 요청할 때 localStorage에서 access token을 가져와 Header의 Authorization에 담아서 보내는 경우 아래와 같이 코드를 작성할 수 있습니다.
axios.interceptors.request.use(
async (config) => {
if (config.url === tokenUrl) {
config.headers.Authorization = null;
config.headers.Authorization = `Bearer ${localStorage.getItem(
"refresh"
)}`;
} else {
config.headers.Authorization = !!localStorage.getItem("access")
? `Bearer ${localStorage.getItem("access")}`
: null;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
Token을 재발급 받는 경우가 아닐 때는 access token을 Authorization에 담아야 하므로 해당 조건을 분기해주어 상황에 맞는 token을 localStorage에서 가져와 Bearer 형식으로 config에 저장해주면 됩니다.
axios interceptors response
interceptors response는 API를 요청한 후에 응답을 가로채어 작업할 수 있습니다.
token이 만료되어 서버로부터 401 에러를 받았을 때 token 재발급 요청을 보낸 뒤에 이전에 보냈던 요청을 재요청하는 코드를 아래와 같이 작성할 수 있습니다.
axios.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const {
config: originalRequest,
response,
} = error;
if (response.status === 401 && !originalRequest.sent) {
originalRequest.sent = true;
try {
const response = await axios.get(tokenUrl);
if (response) {
const { accessToken, refreshToken } = response.data;
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
localStorage.setItem(accessToken, "access");
localStorage.setItem(refreshToken, "refresh");
}
} catch (_error) {
const {
response: { data },
} = _error;
if (data.code === 403) {
localStorage.removeItem("access");
localStorage.removeItem("refresh");
window.location.href = "/";
}
}
return axios(originalRequest);
}
return Promise.reject(error);
}
);
}
}
response callback 함수의 첫번째 인자에는 정상적인 응답을, 두 번째 인자에는 에러 응답을 전달받게 됩니다. access token이 만료되었을 때 에러 응답을 받기 때문에 두 번째 인자에서만 callback 함수를 정의하였습니다.
refresh 요청이 끝나고 재요청을 보냈을 때 에러가 발생한 경우 재귀적으로 loop가 발생할 수 있기 때문에 이를 방지하기 위한 config.sent를 true로 설정해주고 조건문에 함께 추가해주었습니다.
refresh 요청 중에 refresh token이 만료된 경우에는 localStorage에 저장되어 있는 token 정보를 삭제한 후에 홈으로 이동시켰습니다.
마치며
JWT는 확장성이 높은 인가 처리 방식이면서 refresh token을 활용하여 보안을 보다 강화할 수 있습니다.
다만 access, refresh token을 어디에 저장하고 어떻게 전달하면서 갱신하고, 다양한 기기에서 다중 로그인을 지원하기 위해 여러개를 어떻게 다룰 것인지에 따라 구현 난이도가 올라가며, 최상의 방법에 대한 문서를 찾는것도 쉽지 않습니다.
작성된 코드를 보면서 불필요한 부분이 무엇인지, 조금 더 개선할 부분이 무엇이 있는지 계속해서 업데이트해봐야 할 것 같습니다.
'개발이야기' 카테고리의 다른 글
Lighthouse를 이용한 성능 개선 - 이미지 최적화 (0) | 2023.04.14 |
---|---|
Lighthouse를 이용한 성능 개선 - 번들링 개선 (0) | 2023.04.12 |