개요
현재 스낵게임은 액세스 토큰과 리프레시 토큰(이하 AT, RT)을 모두 쿠키로 이관중이다.
AT, RT에는 각각 7일, 30일의 유효기간을 두었고, 쿠키의 수명 역시 동일하게 설정했다.
그러나 만료된 토큰이 리프레쉬되는 일은 없었다.
무엇이 문제였을까?
발생한 문제
현재 클라이언트는 쿠키 제어권이 없고, 서버의 응답에 의존한다. (읽기도 불가)
XSS 공격을 원천 차단하기 위한 HttpOnly
설정을 했기 때문이다.
하지만 문제는 AT가 만료되었을 때, “서버도” 토큰을 읽을 수 없다는 것이다.
AT가 만료되었을 때 이것을 담은 쿠키도 함께 만료되므로, 브라우저가 쿠키를 요청에 싣지 않았기 때문이다.
상황
만료 시 예상 시나리오
- 클라이언트 API 호출
- 서버가 AT를 검사
- 예외 상황을
action
과 함께 응답 - 클라이언트의 토큰 재발급 요청
{
"action": "REISSUE",
"messages": [
"토큰이 만료되었습니다"
]
}
실제 시나리오
- 클라이언트 API 호출
- 서버가 AT를 검사
- 만료된 쿠키가 실리지 않아 AT 검사 실패
{
"action": null,
"messages": [
"토큰을 읽지 못했습니다"
]
}
고민
‘브라우저는 만료된 쿠키를 요청에 싣지 않는다. 그래서 서버는 AT를 읽을 수 없다.’ 가 현재 상황이다.
이에 ‘토큰을 하나 더 만들고, HttpOnly
를 false
로 한 후 클라이언트에서 접근을 허용하자’라는 의견이 나왔다.
하지만 이는 AT를 쿠키로 이관한 목적과 반대되며, 결국 문제를 해결하지 못하고 겉돈다는 생각이 들었다.
해결
AT 쿠키의 만료 시간을 RT 쿠키(30일) 만큼 연장한다.
AT가 만료되어도 만료 상태 그대로 전송되기 때문에 서버가 AT를 읽을 수 있게 된다.
AT가 너무 오래 저장되는 것 아닌가요?
‘오랫동안 저장한다’라는 행위 때문에 탈취에 대한 의문이 생길 수 있다.
그러나 보안상의 문제는 없다. 만료된 토큰은 인증의 의미가 사라진, 효력 없는 텍스트일 뿐이기 때문이다.
Wrap-up
길고 험난했던 ‘하위 호환성을 고려한 토큰 저장소 이관 작업’을 마친다.
우리는 다음과 같은 과정을 거쳤다:
- ‘헤더 토큰’만 지원한다.
- ‘헤더 토큰’ 및 ‘쿠키 토큰’을 지원한다.
- ‘헤더 토큰’에 대한 지원을 제거한다.
이 과정을 통해 쿠키에 대한 더 깊은 이해는 물론, 하위 호환성과 브라우저 간의 보안 문제를 더 깊게 생각해보는 계기가 되었다.
CSRF 이슈는 아직 다루지 않았는데, 후에 보완하는 것이 과제로 남았다.