
이번에 로그인 관련 로직이 조금 수정되는 일이 있었다.
로그인은 보통 제품 초기에 한 번 만들어두면 이후에는 크게 손댈 일이 없다 보니, 관련 로직을 제대로 이해하지 못하고 있는 부분들이 많아서 이번 기회에 인증 구조에 대해 공부해보았다...!
코드를 보기 전에, 흐름을 말로 정리해보는 게 훨씬 이해가 쉬웠다.
1. 로그인 성공
→ 서버에서 access token 발급
→ refresh token은 쿠키로 설정
2. 이후 API 요청
→ access token을 Authorization 헤더로 전송
3. (만약) access token이 만료됨
→ 서버에서 401 응답
4. 401을 감지
→ refresh token으로 refresh 요청
→ 새 access token 발급
5. 원래 요청을 다시 시도
여기서 개인적으로 헷갈렸던 점은,
refresh token은 프론트에서 직접 다루지 않는 경우가 많다는 점이다.
refresh token은 보통 HttpOnly 쿠키로 관리되고, 요청 시 브라우저가 자동으로 함께 전송해준다.
먼저 개념부터 간단히 정리해보면:
Access Token
Refresh Token
굳이 토큰을 두 개로 나누는 이유는 결국 보안 때문이다.
만약 access token이 탈취되더라도,
유효기간이 짧기 때문에 피해를 최소화할 수 있고,
더 중요한 refresh token은 상대적으로 더 안전한 방식(HttpOnly 쿠키 등)으로 관리할 수 있다.
React 같은 SPA 환경에서 자주 볼 수 있는 인증 패턴은 대략 이런 형태다.
이 방식의 특징은,
access token을 브라우저 저장소에 영구적으로 남기지 않고 필요할 때만 재발급받는 흐름이라는 점이다.
interceptor의 역할에 대해서도 헷갈렸는데,
interceptor가 요청을 보낸 후 -> 서버에서 401이 왔을 때 refresh를 시도한다.
api.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401) {
const newToken = await refreshAccessToken()
if (newToken) {
error.config.headers.Authorization = `Bearer ${newToken}`
return api.request(error.config)
}
}
return Promise.reject(error)
}
)
이 구조 덕분에,
실제로는 401이 한 번 발생하더라도 refresh가 성공하면 최종적으로는 200 응답만 보이게 되는 경우가 많다.
토큰을 어디에 저장할지는 항상 선택의 문제인데, 각 방식의 장단점은 간단하게 이렇다.
localStorage
HttpOnly 쿠키
메모리 상태
그래서 access token은 메모리에 두고,
refresh token은 쿠키로 관리하는 조합이 자주 선택되는 것 같다.
이번에 정리하면서 느낀 건,
코드보다 흐름을 먼저 이해하는 게 훨씬 중요하다는 점이었다.
이런 질문에 답이 생기고 나니,
이전보다 로그인 관련 코드를 훨씬 편하게 읽을 수 있게 됐다 🙂