실제 로그인 구현을 하다보면 액세스토큰과 리프레시토큰을 구현해야하는 경우가 많습니다.
저는 프로젝트에 참여하면서 액세스 토큰과 리프레시 토큰을 어디에 저장하는게 가장 이상적인가? 에 대해서 늘 궁금했습니다.
주로 로컬스토리지나 세션 스토리지에 저장했던 프로젝트가 다수였지만,
로컬스토리지나 세션스토리지는 보안에 있어서 굉장히 취약하다고 느껴졌기 때문입니다.
그래서 프론트엔드 쪽에서는 어디에 저장하는게 가장 이상적인가? 에 대한 아티클을 가져오게 되었습니다.
액세스토큰과 리프레시 토큰을 저장할 수 있는 곳은 아래와 같이 2가지가 있습니다.
- 로컬스토리지, 세션스토리지
- 쿠키
로컬스토리지, 세션스토리지
많은 분들이 서버로부터 토큰을 전달받으면 로컬스토리지나 세션스토리지에 저장하는 경우가 많습니다.
이 두가지방법은 장단점이 분명합니다.
- 장점
- 클라이언트 쪽에서 자바스크립트로 쉽게 저장하고 꺼내서 쓸 수 있다.
- 단점
- 하지만 자바스크립트로 접근할 수 있다는 점에서
XSS
공격에 취약하다. - 클라이언트 쪽에서 강제적으로 수정하고 삭제할 수 있다.
- 데이터가 너무 공개적으로 보인다.
- 하지만 자바스크립트로 접근할 수 있다는 점에서
스토리지들의 문제점
매번 토큰들을 로컬스토리지, 세션스토리지에 저장하고 했는데 토큰은 요청때마다 내밀게 되는 인증서인데,
이게 수정도 가능하고 삭제할 수 있다는 점 자체가 실제 큰 대규모 서비스에는 적합하지 않다고 생각했습니다.
지갑에 넣어서 꽁꽁숨겨두고 관리해도 모자를 판에, 브라우저에서 너무 쉽게 까서 열어볼 수 있다는 점도
프로젝트에 적용하면서 개인적으로도 매번 리팩토링을 하면 참 좋겠다고 생각했던 부분이었습니다.🥲
쿠키
토큰들을 쿠키에 담을 수도 있습니다. 쿠키는 로컬스토리지와 세션스토리지의 취약점을 보완해줄 수 있습니다.
httpOnly
옵션을 사용해서 자바스크립트로 접근이 불가능하게 할 수 있고, 또한 secure
옵션을 통해서
http
통신 자체를 하이재킹 당해도, https
의 암호화로 인해 쿠키의 값을 알아내기 어렵습니다.
위의 이야기에 따르면 스토리지들보다 쿠키에 토큰들을 담아 전송하는 방식이 적합해보입니다.
그렇다면 쿠키는 무한정 안전할까요?
그런 것은 아닙니다. 대신 쿠키에 토큰을 담게 되면 CSRF
공격에 취약하게 됩니다.
쿠키를 탈취할 수도 있지만 제 3자가 클라이언트가 어떤 사이트에 로그인되어있는 상태에서 어떤 버튼을 누르거나
사이트에 접속했을 시, 클라이언트가 의도치 않은 요청을 서버에 보내게 할 수 있습니다.
따라서 주로 인증에 사용되는 토큰이 아닌 리프레시 토큰 같은 경우에는 쿠키에 저장되어도 됩니다.
왜냐면 리프레시 토큰으로 액세스 토큰을 재발급 받는 요청 외에는 인증이 필요한 작업들에는 리프레시 토큰으로는 접근할 수 없기 때문입니다. 즉, 대부분의 요청에 필요한 것은 리프레시 토큰이 아니기 때문입니다.
쉽게 말하면 보안에 있어서 액세스 토큰이 더 중요하다 이거죠..🥹
물론 리프레시 토큰으로 무한정 액세스 토큰을 발급받을 수 있기 때문에, 이에 대한 대응이 필요합니다.
위험성을 최대한 줄이기 위해, 리프레시 토큰을 RTR(Refresh Token Rotation)
방식을 도입할 수 있습니다.
Refresh Token Rotation (RTR)
리프레시 토큰으로 액세스 토큰을 발급받을 때,
리프레시 토큰을 새 것으로 교체서 리프레시의 사용을 단 한번만 가능하도록 하는 것
이렇게 하게되면 사용하지 않은 리프레시 토큰을 탈취하면 한 번은 액세스 토큰을 발급받을 수 있지만,
탈취된 리프레시 토큰이 무한정 사용되는 것은 막을 수 있습니다.
정리
- 장점
httpOnly
옵션을 통해 자바스크립트 접근 불가능secure
옵션으로 통신 자체의 보안성 유지
- 단점
- 쿠키가 탈취되거나 탈취당하지 않은채로도 CSRF 공격을 당할 수 있음
- 리프레시 토큰 탈취 시, 액세스 토큰 무한정 발급 가능
- 따라서 RTR 방식 도입 필요
결론적으로 쿠키가 로컬스토리지, 세션스토리지에 저장하는 것 보다는 훨씬 안정적으로 보이므로,
리프레시 토큰은 쿠키에 저장하는게 나아보입니다.
액세스 토큰은 어디에?
액세스 토큰은 CSRF 위험에 노출되면 안되니 쿠키에 저장해선 안됩니다.
그렇다면 액세스 토큰은 도대체! 어디에 저장해야할까요?🧐
액세스 토큰은 자바스크립트 변수로 저장하는 방법이 있습니다.
변수로 저장된 액세스 토큰은 XSS 공격으로 탈취할 수 없고, 당연히 CSRF 공격을 당할 가능성도 없습니다.
만약 리액트를 사용하고 있다면, 리액트는 SPA
이므로, 페이지를 이동하는 것처럼 보여도 페이지가 실제로 이동하는 것이 아니기 때문에 변수가 그대로 유지됩니다. 단, 새로고침을 하면 변수가 날아갑니다.
이 경우에 추가로 리프레시 토큰가지고 액세스 토큰을 발급받는 API를 활용하면 됩니다.
새로고침 이슈 해결법
실제로 Redux
나 React-Query + Context API
를 사용할 때,
새로고침을 하거나 애플리케이션을 껐다가 다시 켰을 시에는 변수들이 날아가는 경험을 한 적이 있습니다.
그렇다면 아래와 같이 문제를 해결해보는건 어떨까요?
새로고침이나 새로 애플리케이션을 켰을 경우 변수에 액세스 토큰이 있는지 확인합니다.
없다면 쿠키에 있는 리프레시 토큰을 활용해 액세스토큰을 발급받고, 다시 변수로 저장합니다.
만약 리프레시 토큰이 만료되었다고 뜬다면 or 없다면, 재로그인을 진행합니다.
state
관리 라이브러리를 사용한다면, 앱을 다시 키거나 새로고침 했을 경우 쿠키에 저장되어있는 리프레시 토큰을 사용해 액세스 토큰을 재발급받아 store
에 저장하면 됩니다.
쿠키, 세션, 토큰에 대한 개념들을 정리하면서 실제로 프로젝트에서 로그인 과정에서 겪었던 문제들과 풀어 이해해보니
가장 중요한건 백엔드 개발자분들과의 소통이라고 느껴지네요..😂