로그인 흐름 이해하기 (feat. Access Token / Refresh Token)

Sara Jo·2026년 1월 17일
post-thumbnail

이번에 로그인 관련 로직이 조금 수정되는 일이 있었다.
로그인은 보통 제품 초기에 한 번 만들어두면 이후에는 크게 손댈 일이 없다 보니, 관련 로직을 제대로 이해하지 못하고 있는 부분들이 많아서 이번 기회에 인증 구조에 대해 공부해보았다...!


🔄 전체 흐름

코드를 보기 전에, 흐름을 말로 정리해보는 게 훨씬 이해가 쉬웠다.

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

    • 실제 API 요청에 사용되는 토큰
    • 보통 유효기간이 짧다
  • Refresh Token

    • Access Token이 만료됐을 때 새 토큰을 발급받기 위한 토큰
    • 상대적으로 유효기간이 길다

굳이 토큰을 두 개로 나누는 이유는 결국 보안 때문이다.

만약 access token이 탈취되더라도,
유효기간이 짧기 때문에 피해를 최소화할 수 있고,
더 중요한 refresh token은 상대적으로 더 안전한 방식(HttpOnly 쿠키 등)으로 관리할 수 있다.


🧩 인증 패턴

React 같은 SPA 환경에서 자주 볼 수 있는 인증 패턴은 대략 이런 형태다.

  • Access Token: 메모리 상태(React state, zustand 등)에 저장
  • Refresh Token: HttpOnly 쿠키로 저장
  • 토큰 만료 처리: API 요청 중 401 응답이 오면 refresh 후 재시도

이 방식의 특징은,
access token을 브라우저 저장소에 영구적으로 남기지 않고 필요할 때만 재발급받는 흐름이라는 점이다.


🔁 interceptor

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, 쿠키, 메모리… 어디에 토큰을 두는 게 좋을까?

토큰을 어디에 저장할지는 항상 선택의 문제인데, 각 방식의 장단점은 간단하게 이렇다.

  • localStorage

    • 구현이 단순
    • 하지만 XSS에 취약
  • HttpOnly 쿠키

    • JS에서 접근 불가
    • 하지만 CSRF 대응이 필요
  • 메모리 상태

    • 새로고침 시 사라짐
    • refresh 구조와 함께 쓰이면 보안상 이점

그래서 access token은 메모리에 두고,
refresh token은 쿠키로 관리하는 조합이 자주 선택되는 것 같다.


✍️ 정리하며

이번에 정리하면서 느낀 건,
코드보다 흐름을 먼저 이해하는 게 훨씬 중요하다는 점이었다.

  • interceptor는 언제 동작하는지
  • refresh는 누가 트리거하는지
  • 토큰은 어디에 저장되고, 왜 그렇게 설계되는지

이런 질문에 답이 생기고 나니,
이전보다 로그인 관련 코드를 훨씬 편하게 읽을 수 있게 됐다 🙂

0개의 댓글