JWT 혹은 OAuth2 의 refresh 토큰을 어디다 저장해야 할까?

요즘 네이버 로그인, 카카오 로그인이나 구글 로그인 등등 소셜 미디어(Social media) 사용자 로그인 처리를 하다 보니 로그인된 상태가 끊임없이 유지되는 것을 구현해야 되더군요. 그러려면 결국 리프래시 토큰(refresh token)을 어디에 저장해 둬야 하나 하는 문제로 골머리를 앓게 됩니다.

접속 토큰(access token) 은 만료시간이 짧기 때문에 좀 더 오픈된 공간에 저장해도 되지만.. 리프래시 토큰은 그러기가 쉽지 않습니다.

웹에서 사용자의 로그인 정보를 범용적으로 저장할만한 공간은 클라이언트 사이드에서는 로컬 스토리지(Local storage), 쿠키(Cookie) 등이 있습니다. 서버 사이드에서는 대표적으로 세션(Session)이나 Database 등에 저장할 수 있겠죠.

과연 어느 장소에 저장해야 할까요? ^^;

1. Local Storage

처음에는 local storage 나 쿠키(Cookie)에 저장하는 방법을 생각해 보았는데요. local storage 는 자바스크립트로 접근이 너무 쉬워서 XSS 공격에 취약하고 보안상 문제 소지가 많아 보입니다.

쿠키의 경우는 HTTPOnly 와 Secure 옵션을 사용하고 CSRF 공격에 대비를 하면 어느정도 보안이 되기때문에 한때는 여기다가 리프레시 토큰을 저장하곤 했습니다.

현재 JWT 형식의 토큰의 경우는 토큰 자체가 추가적인 값을 가지기도 하고 암호화되기 때문에 HTTPOnly 토큰이 가장 효율적인 것으로 생각됩니다. 다만 HTTPOnly 쿠키는 클라이언트에서 토큰 접근이 안되므로 토큰에 들어있는 정보를 접근하려면 관련 처리를 서버 쪽에서 해줘야 합니다.

하지만 쿠키도 역시 불안하기는 마찬가지라서 좀 더 좋은 방법이 없나 궁리를 해보게 되는데요.

3. 결국은 Server Side

역시 가장 좋은 방법은 서버사이드에 저장해두는 것이겠죠.

가장 간단한 방법은 세션에 저장하고 세션 만료 주기를 왕창 늘려주는 방법도 있는데요. 이건 사용자가 얼마 안 된다면 써먹어 볼 수 있겠지만 역시 사용자가 많은 경우에는 말도 안 되는 방식이고… 애초에 세션을 사용하지 않으려고 만든 게 액세스 토큰 방식인데 뭔가 아닌거 같더라고요.

그래서 생각해 낸 것이 DB에 저장하는 방법입니다.

DB에 실제 리프레시 토큰 값을 저장하고 이때 index 값을 쿠키나 로컬 스토리지에 저장하는 방법입니다. 쿠키에 저장하는 경우 만료 기간을 한 달이나 1년 등으로 길게 잡으면 충분히 끊임없이 로그인 된 상태를 유지할 수 있습니다.

이렇게 하면 리프레시 토큰 값은 노출시키지 않고 크게 의미 없는 index 값 만 노출되므로 보안상 좀 더 안전하게 저장할 수 있습니다.

이를 조금 응용해서 저장하는 인덱스 값도 유추가 쉬운 단순 순번 값보다는 사용자 아이디와 추가적인 값을 조합해 SHA256 등을 활용해 hash값을 생성해 사용하면 보안에 더욱 유리할 것입니다. 물론 DB에도 리프레시 토큰 값만이 아니라 사용자 아이디 값을 추가로 저장해야 합니다.

이렇게 되면 결과적으로 리프레시 토큰 값에 대한 해시테이블(hash table) 혹은 해시 맵(hash map)을 구현하게 됩니다.

마지막으로 이 해시값을 HTTP Only, Secure 옵션을 걸어놓은 쿠키로 저장해서 사용하면 됩니다. 즉 2번 쿠키 저장방법과 혼합해서 사용하면 됩니다.

참고로 카카오 개발자 페이지에서 카카오 로그인 Javascript 문서 중 refresh token 관련된 정보(https://devtalk.kakao.com/t/javascript-api-sdk-refresh-token/105942)를 봐도 클라이언트 단에서 refresh token 을 노출하지 않도록 REST API로만 처리할 수 있도록 정책이 바뀌고 있는 것을 확인할 수 있습니다.

https://developers.kakao.com/docs/latest/ko/kakaologin/js#advanced-guide

만료기간이 짧은 access token 의 경우에는 저장 위치를 클아이언트단에서도 할 수 있지만 아무래도 만료기간이 긴 refresh token 의 경우 탈취된다면 크게 문제가 될 수 있기 때문이라고 생각됩니다. refresh token 은 가능한한 쉽게 접근할 수 없는 곳에 저장하는 것이 정답인듯 합니다. ^^

참고자료