이전에 프로젝트를 만들면서 JWT를 이용한 토큰 기반 인증을 구현했었다.
이 토큰은 세션과는 달리 다양한 플랫폼에서 토큰만으로 인증할 수 있다는 장점이 있어 유용하다.
그런데 내가 사용한 방식에는 문제가 있었다. 토큰 유효기간이 무제한이라 한번 탈취당하면 피해를 걷잡을 수 없는 것이다.
그 당시에 나는 JWT를 이용하면서 깊게 공부하지 않았고, 당연히 제대로 쓰지 않으면 이러한 문제점이 있다는 것도 알지 못했다.
그리고 그 당시의 기술부채가 쌓여서 지금 돌아왔다.
지금이라도 해결방법을 알아보자
문제를 해결하기 위해 나는 JWT의 유효시간을 설정하는 방법을 찾아보고 공부하면서 Refresh Token 이라는 키워드를 알게 되었다.
만약 우리가 토큰의 유효기간을 하루로 설정했다고 생각해보자. 그러면 길지 않은 시간이지만 토큰이 탈취 당했을 때, 하루라는 시간동안 서버는 탈취당한 사람과 탈취한 공격자를 구분할 수 없게 된다.
이럴 때, 사용할 수 있는 것이 바로, Refresh Token이다.
Refresh Token은 사용자 인증이 아닌 새로운 Access Token을 생성하는 용도로만 사용된다.
방법은 다음과 같다.
이렇게 되면 공격자는 Access Token을 탈취하더라도 짧은 유효 기간이 지나면 사용할 수 없다는 장점이 있다.
물론 정상적인 클라이언트는 유효기간이 지나더라도 Refresh Token을 사용하여 새로운 Acccess Token을 생성하여 사용할 수 있으니 문제가 없다.
즉, 짧은 시간동안 사용할 수 있도록 하고 주기적으로 재발급을 하도록 하는 것이다. 이렇게 하면 앞에서 말했듯 토큰이 유출되어도 피해를 최소화할 수 있다.
정상적인 클라이언트도 짧은 주기마다 다시 로그인해서 Access Token을 발급받아야 하는 것이다. 그래서 유효 기간이 긴 Refresh Token을 사용하는데 정상적인 사용자는 Access Token이 만료됐다면 서버측에 Refresh Token을 전송하여 다시 로그인할 필요 없이 Access Token을 발급받을 수 있다. 당연히 이 Refresh Token이 없는 공격자는 다시 토큰을 발급받을 수 없기 때문에 보안 측면에서 좀 더 안전하다고 할 수 있다.
Refresh Token이 탈취되는 경우가 있을 수 있다. 그렇다면 어떻게 해야할까?
중요한 것은 발급된 토큰 자체는 그냥 그 JWT 문자열 자체로 존재하는 것이기 때문에 클라이언트나 서버측에서 전역적으로 만료시킬 수 있는 개체가 아니다. 그렇기 때문에 토큰의 유효 기간이 지나기 전까지는 토큰을 데이터베이스에 저장할 필요가 있다.
서버에서는 데이터베이스에 토큰을 저장할 수 있다. 그렇다면 클라이언트에서는 어디에 저장할 수 있을까
쿠키, 로컬 스토리지 등 다양한 곳이 있지만 스택오버플로우에서는 http-only 속성이 부여된 쿠키에 저장하는 것을 권장하고 있다.
해당 속성이 부여된 쿠키는 자바스크립트 환경에서 접근할 수 없기 때문이다. 그래서 XSS나 CSRF가 발생하더라도 토큰이 누출되지 않는다. 일반 쿠키나 브라우저의 로컬 스토리지는 자바스크립트로 자유롭게 접근할 수 있기 때문에 보안 측면에서는 권장되지 않는다.
어떤 기술을 사용하든 제대로 알고 사용하지 않으면 부작용이 생길 수 있다. 기술을 사용하기 전 확실하게 공부해두고(특히 주의할점) 기술을 적용하는 습관을 들이자