쿠키 세션 방식은
어디서 무너졌나
액세스/리프레시 토큰 체계 이전의 주류는 쿠키 기반 세션(Session Cookie)이었습니다. 로그인하면 서버가 세션 ID를 만들어 DB에 저장하고, 그 ID를 쿠키로 브라우저에 보냅니다. 이후 모든 요청마다 서버는 DB에서 세션을 조회해 인증합니다. 작동은 했지만, 세션 ID 탈취 시 피해 제어와 서버 확장 두 곳에서 한계가 드러났습니다.
① 세션 ID 탈취 = 만료까지 무제한 사칭 — 서버는 올바른 ID를 가진 요청을 무조건 통과시킵니다.
② 서버가 모든 세션을 DB에 보관 — 서버를 여러 대로 늘릴 때(Scale-out) 세션 공유가 복잡해집니다.
액세스/리프레시 토큰 분리는 ①을 해결합니다: 액세스 토큰을
15분으로 짧게 두면 탈취되어도 피해 기간이 제한됩니다.
리프레시 토큰은 서버 DB에 기록해 의심 시 즉시 무효화할 수 있습니다.
로그인하고
API를 호출하기까지
사용자가 로그인하면 서버는 두 가지 토큰을 발급합니다. 액세스 토큰은 "지금 당장 쓸 수 있는 출입증"이고, 리프레시 토큰은 "출입증이 만료됐을 때 새 출입증을 받는 재발급권"입니다. 출입증은 짧게, 재발급권은 길게 유효하게 설정합니다.
15분처럼 짧게 설정합니다. 누군가 훔쳐도 곧 만료되어 쓸 수 없게 되기 때문입니다.
리프레시 토큰은 7일~30일로 길게 설정하되, 서버 DB에 기록해서 필요하면 언제든 무효화할 수 있습니다.
이 두 역할을 분리한 덕분에 보안과 편의성을 동시에 얻을 수 있습니다.
저장 방식: 액세스 토큰은 JS 메모리에, 리프레시 토큰은
httpOnly 쿠키에 저장합니다.
httpOnly 쿠키는 자바스크립트에서 직접 읽을 수 없어 XSS 공격에 안전하고, 브라우저가 요청 시 자동으로 함께 전송합니다.
Authorization: Bearer {액세스토큰} 헤더로 요청합니다.액세스 토큰이
만료됐을 때
15분이 지나 액세스 토큰이 만료되면 서버는 401 오류를 반환합니다. 클라이언트는 사용자 모르게 리프레시 토큰을 이용해 새 액세스 토큰을 받고, 원래 요청을 다시 보냅니다. 사용자는 아무것도 느끼지 못합니다.
401(출입증이 더 이상 유효하지 않다는 신호)을 받으면,
자동으로 재발급권(리프레시 토큰)을 써서 새 출입증을 받아온 뒤 원래 요청을 다시 시도합니다.
사용자는 아무것도 느끼지 못하고 앱이 끊김 없이 동작합니다.
POST /auth/refresh를 호출합니다. 리프레시 토큰은 httpOnly 쿠키로 자동 전송됩니다.리프레시 토큰도
만료됐을 때
리프레시 토큰도 만료(또는 무효화)되면 더 이상 갱신이 불가능합니다. 서버는 두 번째 401을 반환하고, 클라이언트는 모든 토큰을 삭제한 뒤 사용자를 로그인 화면으로 이동시킵니다.
401이 오면 "세션 완전 만료"로 처리합니다.
JS 메모리의 액세스 토큰을 삭제하고, 리프레시 토큰 쿠키는 서버가 만료된 Set-Cookie를 내려보내 제거합니다 (httpOnly 쿠키는 JS가 직접 삭제할 수 없습니다).
서버가 강제 로그아웃(토큰 무효화)을 할 때도 이 경로로 처리됩니다.
안전하게
로그아웃하기
로그아웃은 단순히 토큰을 삭제하는 것이 아닙니다. 서버에 리프레시 토큰을 알려주어 DB에서 무효화해야 탈취된 리프레시 토큰으로 새 액세스 토큰을 발급받는 것을 막을 수 있습니다.
POST /auth/logout을 호출합니다. 리프레시 토큰은 httpOnly 쿠키로 자동 전송됩니다.Set-Cookie를 포함해 브라우저의 리프레시 토큰 쿠키를 제거합니다. 로그인 화면으로 이동합니다.리프레시 토큰도
교체하기
리프레시 토큰을 쓸 때마다 새 것으로 교체하는 방식을 토큰 로테이션(Rotation — 갱신할 때마다 재발급권도 새것으로 바꾸는 방식)이라고 합니다. 서버는 이전 재발급권을 "사용됨"으로 기록해 두고, 누군가 그것을 다시 가져오면 탈취로 판단할 수 있습니다. 보안이 중요한 서비스에서 널리 쓰이는 패턴입니다.
"사용됨(revoked)" 상태로 DB에 기록합니다.
그래야 나중에 같은 토큰이 다시 들어왔을 때 "이미 썼던 재발급권을 또 쓰려 한다"는 것을 감지할 수 있습니다.
탈취된 토큰
재사용 공격 감지
예를 들어 사용자의 기기가 해킹되거나 네트워크가 탈취되어 공격자가 리프레시 토큰을 손에 넣었다고 가정합니다. 공격자가 그 토큰으로 먼저 갱신 요청을 보내면, 정상 사용자가 뒤이어 갱신할 때 이미 "사용됨" 처리된 토큰을 보내게 됩니다. 서버는 이를 재사용 공격(Reuse Attack)으로 판단해 이 로그인에서 발급된 모든 토큰을 한번에 무효화합니다.
단, 공격자가 이미 발급받은 액세스 토큰은 만료 전까지 일시적으로 유효할 수 있습니다. 이를 즉시 차단하려면 서버가 모든 요청에서 세션 상태를 추가로 검사해야 합니다. 그래서 액세스 토큰 유효 시간을 짧게 설정하는 것이 중요합니다.