JWT는 어디에 저장해야 하는 걸까? 😕

     

     

    * 본문은 어디에 저장하는 게 적합한지 명확한 결론을 내린 글이 아님

    같은 고민으로 해답을 찾으러 왔다면 도움이 안될 수 있습니다.

     


     

    Next.js로 로그인/회원가입을 구현하게 됐다.

    로그인 기능 구현은 이번이 두번째인데 여전히 고민되는 이슈는 'JWT를 어디에 저장해서 사용해야하는가'였다.

    로컬스토리지에 저-장★

    >. ㅇ

     

    이전에 React 환경에서 처음 로그인을 구현했을 때는 JWT를 로컬스토리지에 저장하는 방법을 사용했었다.

     

    이번 프로젝트에도 백엔드 개발자 분께서 JWT를 사용하겠다고 하셔서 로그인에 성공 시 전달받은 액세스/리프레스 토큰을 로컬스토리지에 저장하는 방식을 택했다.

     

    세션스토리지는 탭을 변경하거나 페이지를 닫았다 다시 열으면 초기화 되기 때문에 '로그인 유지'라는 부분에서 UX가 매우 떨어질 것이기 때문에 사용성 측면에서는 origin이 동일할 경우 새로고침을 하거나 탭을 이동하더라도 유지가 되는 로컬스토리지가 더 적절하다고 판단했다.

     

    그런데 JWT 보안은... 어떻게 할까요..? 🤔

    JWT를 웹 스토리지에 저장하게 될 경우 클라이언트에서 토큰을 탈취당하게 되면 나인척 다른 사람이 사용할 수 있게 되는 문제가 발생할 수 있다.

     

    JWT의 만료 시간을 짧게 가져가라는 얘기가 있는 이유도 탈취 당할 가능성이 있기 때문에 만료를 빨리 시키자는 것이다.

    프레시한줄 알고 가져갔지만 유효기간 만료로 인해 상해서 사용할 수 없는 JWT라면 무용지물이기 때문.

     

    지난번 처음 로그인을 구현할 때는 로컬스토리지에 저장한 액세스 토큰과 리프레스 토큰을 그냥 저장하는 것에 대한 보안적인 이슈가 있을 수도 있겠다는 생각이 들어서 백단에서 받은 토큰을 프론트에서 암호화 해서 로컬스토리지로 저장시키는 방법을 사용했던 적이 있다.

    보안적인 부분을 조금 개선할 수 있을지는 모르겠지만, 확실히 안전한 방법인가하는 물음에 대해서는 사실 클라이언트 단에서 암호화를 한 거라 보안적인 이슈는 발생할 가능성이 여전히 존재한다고 생각한다. (그리고 프론트에서 한번 더 복호화를 해야하는 게 상당히 귀찮았다...)

     

    JWT는 쉽게 복호화된다는 점에서 사용자의 민감한 정보가 담기지 않도록 해야한다.

    꼭 필요한 정보만 담는 편이 좋다는 것이다.

     

    실제로 본인이 첫 팀 프로젝트를 진행할 때 백단에서 보내준 JWT를 복호화했었는데 id와 password가 함께 포함되어 있는 경우도 있었다.......... 🙄🙄

    그때 로그인 구현은 내 담당이 아니라서 크게 신경쓰지 않고 있었는데, 뭔가 로그인 관련해서 작은 이슈가 있었던 것으로 기억하는데 그러다가 JWT 복호화했다가 비밀번호까지 담아 보낸걸 찾게된 케이스..

    (그 다음 프로젝트에서는 혹시?하는 마음에서 JWT 받는 즉시 복호화부터 하게 됐다는 TMI)

     

    JWT 에서 액세스 토큰을 넣으면 디코딩된 payload를 웹에서 쉽게 확인할 수 있다.

    현재 토이프로젝트로 진행하고 있는 프로젝트의 액세스 토큰을 넣었다.

    디코딩된 부분을 보면 타입이 JWT이고, JWT 암호화 알고리즘으로 HS256 알고리즘을 사용했다는 정보와

    페이로드쪽에는 실제 해당 사용자의 정보가 담겨있다. exp는 토큰의 만료기간을 의미한다.

    시그네이처는 JWT를 조작해서 막 만든 JWT가 유효하면 안되기 때문에 검증하는 부분이다.

     

    JWT를 어디에 저장해야하는가와 관련해서 서치해보면 굉장히 보안적인 부분과 밀접한 내용의 글들이 많이 보이는 만큼 어디에 저장하는게 적절할지 혼동이 생겼었다.😨

     

    현재 액세스 토큰과 리프레스 토큰을 그대로 로컬스토리지에 저장하는 방법으로 구현한 근거는 다음과 같았다.

    1. JWT에 민감한 정보가 없다.

    2. 내가 생성한 응답을 저장해서 다시보기 위한 용도의 회원 서비스이다.

     

    해당 서비스 특성상 보안적인 부분에서 고민까지 해야할까하는 부분에서는 불필요한 고민이 될 수도 있겠다는 생각은 든다. 이 시간에 다른 부분을 리팩토링하는 게 더 효율적일 수도 있다는 생각이다.

    물론 현재 이 서비스가 확장성없이 이대로만 존재했을 때에 한정으로 보안적인 부분은 크게 중요하지 않을 것이다.

    어떤 서비스가 추가적으로 확장되어 들어갈지는 아직 논의되진 않았지만, 어렴풋하게 이런 기능으로 확장해볼수 있겠다는 얘기가 나왔던터라 혹시, 만약을 대비해서 한번 쯤 고민을 해볼 필요성이 있다고 느꼈다.

    그래서 찾아본 다른 방법들

    - 액세스 토큰을 프라이빗 변수로 저장하고 리프레시 토큰을 리퀘스트로 전달해서 새 액세스토큰을 발급받는 방식을 사용하는 것을 추천하는 글을 봤는데, 결국 매 요청마다 새 액세스 토큰을 발급받는 방법으로 보안 이슈를 해결하자는 내용으로 이해했다. JWT 토큰의 취지와 적합하고 적절한 방식인가..하는 의문이 들었다. (불필요한 네트워크를 여러번 태워야 하지않을까싶은 느낌.. 보안을 위해 서버쪽 부하를 더 걸겠다는 것 같다.)

     

    - 쿠키에 저장하기. 로컬스토리지 만큼이나 자주 사용되는 방법일 것이다.

    스토리지 저장은 XSS에 취약하니 httpOnly 쿠키에 저장하자와 같은 내용인데,

    쿠키도 결국 CSRF 공격에 취약하다는 점에서 절대적인 방법은 아닌 것 같다. 


    정말 베스트한 방법은 없는 것 같고 어떤걸 채택해도 트레이드 오프는 필요한 것 같다.

     

    Same site 쿠키의 경우에는 같은 도메인을 쓴다면 CSRF 공격에 안전하다는 내용을 보긴 했는데

    액세스 토큰의 수명을 줄여서 자주 교체해주는 방법이 심플할 것 같다는 생각이 든다. (심플 이즈 베스트..!?)

     

    앞으로도 고민해볼 주제일 것 같은 느낌.

    참..... 어렵다.. 😓..

     

    그래서 뭐로 결정했냐고 묻는다면

    당장은 로컬스토리지 그대로 사용할 생각입니다......


    참고 자료

    LocalStorage vs. Cookies: JWT 토큰을 안전하게 저장하기 위해 알아야할 모든것

    The Ultimate Guide to handling JWTs on frontend clients (GraphQL)

    브라우저 쿠키와 SameSite 속성

    JWT는 어디에 저장해야할까? - localStorage vs cookie

    댓글