토큰을 저장하는 위치
토큰은 일반적으로 쿠키, 스토리지, 로컬 변수 등에 저장한다.
쿠키는 HttpOnly를 true로 주게되면 script로 접근이 불가능하기 때문에 스크립트를 실행하는 XSS를 방지할 수 있지만, 모든 HTTP 요청에 자동으로 포함되기 때문에 요청을 위조하는 CSRF 공격으로부터 안전하지 않다.
따라서 Secure 옵션으로 https에서만 전송하도록 설정하고, sameSite 설정을 통해 cross site 전송을 막아야한다.
반면 스토리지는 HTTP 요청에 자동으로 포함되지 않기 때문에 CSRF를 방지할 수 있지만, script로 접근할 수 있다.
위와 같은 이유로 액세스 토큰을 함수 스코프 내에서만 동작하는 로컬 변수에 저장하는 것이 가장 안전하다.
액세스 토큰을 발급받는 용도 외에는 아무런 권한이 없는 리프레시 토큰의 경우 CSRF를 통해 액세스 토큰을 재발급 받더라도 발급된 토큰은 유저의 브라우저에 남기 때문에 공격자는 재발급된 토큰을 알 수 없다. 따라서 리프레시 토큰은 쿠키에 저장한다.
어차피 XSS와 CSRF는 XSS 필터나 CSRF 토큰 등 여러가지 방법으로 방어를 하겠지만, 방어 로직을 추가했더라도 일단은 가장 안전한 곳에 보관하는 것이 좋다.
토큰을 탈취 당한 경우에 대한 대처
보안에 신경을 쓰더라도 토큰을 탈취 당할 수 있다. 이때 다른 사람의 인증 정보를 가지고 하이재킹과 같은 공격이 발생할 수 있는데, 예를 들어 공용 PC를 사용했는데 로그아웃을 하지않아서 토큰이 노출된 경우 악의적인 사용자가 해당 토큰으로 서버와 요청을 주고받을 수 있게된다.
이런 상황에 탈취 당한 액세스 토큰의 만료 기간을 1년으로 주었다면, 공격자는 1년 동안 액세스 토큰을 사용할 수 있게 된다.
그렇기 때문에 액세스 토큰의 만료 기간을 짧게 잡아서 피해를 최소화하고, 만료 주기가 너무 짧아서 빈번한 로그인 요청이 발생하지 않도록 리프레시 토큰을 활용해서 유저가 불편함을 느끼지 않도록 하는 것이다.
또한 로그아웃과 같은 기능에서 기존에 발급된 토큰들을 사용할 수 없도록 해야하는데, 이미 발급된 토큰을 만료시킬 방법은 없다.
따라서 액세스 토큰의 경우 블랙리스트에 추가해서 관리하고, 리프레시 토큰의 경우 화이트리스트 방식을 사용해서 DB에 존재하는 토큰만 사용할 수 있도록하고, 로그아웃 시 DB에서 토큰을 삭제하는 방법을 활용한다.
이때 리프레시 토큰의 경우 사용 기간이 길기 때문에 탈취 당했을 경우 오랜 시간 문제가 발생할 수 있다.
이를 방지하기 위해 토큰이 탈취 당해도 사용할 수 없도록 토큰을 일회용으로 사용하는 RTR(Refresh Token Rotation) 방식을 고려해볼 수 있다.
RTR은 액세스 토큰 재발급 시, 리프레시 토큰도 재발급해서 DB에 기존 리프레시 토큰을 삭제하고 새로운 리프레시 토큰을 저장하는 방식이다.
토큰 저장 과정
로그인 후 토큰 발급
- 백엔드 → 로그인 성공 시 access token, refresh token을 발급한다.
- 백엔드 → access token은 authentication header에 넣고, refresh token은 쿠키에 저장하여 httpOnly / secure / SameSite 옵션을 설정한다.
- 프론트엔드 → access token을 로컬 변수에 저장한다.
토큰 재발급
- 프론트엔드 → access token이 만료되었거나, 페이지 이동으로 사라졌을 시 access token 재발급 요청
- 백엔드 → refresh token을 DB에서 조회하고 존재하는 경우 access token과 refresh token을 재발급한다.
- 백엔드 → 기존 refresh token을 DB에서 삭제하고 새로 발급 받은 refresh token을 저장한다.
- 백엔드 → 새로운 refresh token을 쿠키에 저장하고, access token을 header에 넣어 반환한다.
토큰 인증
- 프론트엔드 → API 요청 header에 access token을 추가한다.
- 백엔드 → header에 있는 access token을 검사하고, 블랙 리스트에 존재하는지도 검사한다.
- 백엔드 → 문제가 없는 경우 토큰의 인증 정보에 해당하는 사용자로써 페이지 접근을 허용한다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!