Refresh Token 이야기

Access Token으로 JWT format을 사용한다고 가정합니다.

JWT (JSON Web Token)

JWT는 Header, Payload, Signature 총 세 파트가 urlsafe-base64 encoding되어 .을 기준으로 이어진 형태로 이루어져 있습니다.

Header에는 이 토큰이 어떤 타입의 토큰이며, 어떤 서명 알고리즘을 사용하였는지에 대한 정보를 담습니다.

Example
{
"alg": "HS256",
"typ": "JWT"
}

Payload에는 iss(issuer), exp(expiration time), sub(subject)등 여러 정보들이 담깁니다. 이 곳에 담기는 정보들은 간단히 base64 디코딩만 하면 노출되는 정보들이기에, 노출되면 안되는 정보를 넣지 않도록 주의해야 합니다.

Example
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

Signature에는 HeaderPayload를 서버의 secret값으로 Header 에서 명시한 서명 알고리즘으로 서명을 한 값이 들어갑니다.

Example
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

사용 시나리오

ClientServer로그인 요청Access Token(JWT) 생성Access Token(JWT) 전달API request with Access TokenJWT 검증API responseClientServer

Access Token & Refresh Token

JWT는 stateless하기 때문에, Access Token이 제 3자에게 유출되었을 경우 토큰을 무효화(revoke) 시키기 어렵습니다. 또한, 그 피해를 줄이기 위해 Access Tokenexp값(유효기간)을 짧게 하는 것이 좋습니다.
Access Token이 가지는 유효기간이 짧고, 만료가 되면 다시 로그인해야 하는 불편함을 줄이기 위해, Access Token이 만료되었을 경우 새로운 Access Token을 받아올 수 있는 토큰인 Refresh Token이라는 것을 사용하게 됩니다.
Access Token의 유효기간은 짧게 가져가면서, Refresh Token의 유효기간은 길게 가져가는 방식으로 로그인이 오랜 기간 유지될 수 있게 하는 것입니다.

Refresh Token의 저장

모바일 앱에서라면 Android의 경우 KeyStore, iOS의 경우 KeyChain 같은 안전한 저장공간에 저장을 할 수 있습니다.
브라우저에서는 어디에 저장하는것이 안전 할까요?
사실 웹페이지에서 아주 긴 시간 로그인상태가 유지되어야 한다는게 조금 이상하긴 합니다. 앱의 경우에는 다시 로그인하는것이 한번 설치하고 한번 로그인 하면 계속 로그인 상태가 유지되는것을 원하지만, 웹의 경우에는 브라우저를 자주 껐다 켰다 하며, 그때그때 로그인을 자주 하기 때문입니다.

그래도 저장한다면 HttpOnlySecure가 적용된 Cookie에 저장을 하는게 안전하지 않을까 생각합니다. Access Token과 달리, CSRF 공격에 당해도 Refresh Token으로는 할 수 있는것이 거의 없으며, HttpOnly가 설정되어있기 때문에 XSS로부터도 안전합니다. 또한, 요즘 SameSite 속성으로 CSRF로부터 어느정도는(?) 안전해지기도 했습니다.

Refresh Token Rotation

브라우저 어디에 저장을 하든 믿을 수 없으면, Refresh Token을 로테이션 시키는 방법도 존재합니다.
Access Token이 만료되고 Refresh Token으로 새로운 Access Token을 받아올때, 새로운 Refresh Token도 받아오는 것이죠.
이렇게 구성하면 Refresh Token이 1회용이 되기 때문에, 재사용 되면 토큰이 유출되었음을 감지할 수 있게 됩니다.

하지만 앞서 말했듯이, 이경우에도 JWT 특성상 유출된 토큰으로 새롭게 발행된 Access Token을 무효화시키기 어렵습니다.
또한, 공격자가 XSS 취약점을 이용하여 주기적으로 Access Token을 훔쳐가는 방식이라면 감지되지도 않습니다.
계속 주기적으로 감시하면서 훔쳐가다가, 유저가 서비스 사용을 멈추면 그 이후에 유저가 다시 접속하기 전까지 Refresh Token을 이용하여 자유롭게 갱신/사용을 할수도 있겠습니다.

웹에선 어떻게 해야 안전할까?

  1. 웹에서 XSS 취약점 존재하지 않게 하기 :
    가장 기본적이지만, 지켜지기 어렵긴 합니다.
  2. Access Token의 유효기간을 짧게 하기 :
    갱신을 자주하게 되지만, 무효화 시키기 어려운 JWT 특성상 유효기간이 짧은것이 피해를 줄이는데 도움을 줄 수 있습니다.
  3. BFF(Backend For Frontend) Pattern + reference token 사용하기 :
    self-contained 토큰인 JWT 대신에 reference token을 사용하게 되면, 무효화가 쉽습니다.
    또한, BFF 패턴을 이용하여 클라이언트와는 Cookie-based Session/reference token을 사용하고, 뒤 API 서버들과는 JWT로 통신하여, 기존에 사용하고 있는 Microservice architecture에서 앞에 Gateway만 하나 두면 그대로 사용 가능합니다.

요약

  1. JWT 에 유저에게 노출되면 안되는 정보는 넣으면 안됩니다.
  2. Access Token의 유효기간은 짧게 설정합니다.
  3. Refresh TokenXSS로부터 안전하도록 HttpOnly, SecureCookie에 저장합니다.
  4. Refresh Token Rotation을 적용하면, Refersh Token의 재사용을 감지할 수 있습니다.
  5. 쿠키에 저장 불가능할 경우, BFF패턴을 이용하여, Access Token/Refresh Token없이 Client와는 reference-tokenCookie에 넣어 통신하고, 뒤 API서버들과는 JWT로 통신하여 stateless함을 유지할 수 있습니다.

틀린 내용이 존재할 수 있습니다. 지적 환영합니다.

References

Share