CSRF와 XSS 관점에서 토큰 저장 전략을 어떻게 봐야 할까

Frontend

프론트엔드에서 인증을 다룰 때 가장 자주 반복되는 질문 중 하나는 이것입니다.

"토큰을 어디에 저장해야 가장 안전할까?"

이 질문에 대한 답은 생각보다 단순하지 않습니다.

왜냐하면 저장 위치를 고르는 순간, 사실은 아래를 같이 고르고 있기 때문입니다.

  • 어떤 공격에 더 취약해질 수 있는지
  • 어떤 공격에는 조금 더 강해질 수 있는지
  • 사용자 경험을 어떻게 가져갈지
  • 구현 복잡도를 프론트와 서버 중 어디에 둘지

그리고 여기서 거의 항상 함께 등장하는 두 축이 있습니다.

  • XSS
  • CSRF

실무에서는 종종 이런 식의 단순한 문장을 보게 됩니다.

  • "쿠키는 CSRF 때문에 위험하다"
  • "localStorage는 XSS 때문에 위험하다"
  • "HttpOnly 쿠키면 가장 안전하다"

이 말들은 일부는 맞지만, 그대로 결론으로 가져가면 부족합니다. 중요한 것은 어떤 저장 방식이 어떤 위협 표면을 줄이고, 대신 어떤 다른 방어를 요구하는가를 같이 보는 것입니다.

이 글에서는 토큰 저장 전략을 아래 흐름으로 정리해보겠습니다.

  1. XSSCSRF를 왜 같이 봐야 하는지
  2. localStorage, sessionStorage, 메모리, 쿠키는 각각 어떤 특성이 있는지
  3. 저장 방식마다 어떤 공격 표면이 달라지는지
  4. 실무에서는 어떤 절충안이 자주 쓰이는지

한눈에 보면

먼저 짧게 정리하면 이렇습니다.

  • localStoragesessionStorage는 자바스크립트에서 읽을 수 있어서 XSS에 특히 민감합니다.
  • HttpOnly 쿠키는 스크립트 직접 읽기를 줄일 수 있지만, 자동 전송 특성 때문에 CSRF 관점을 같이 봐야 합니다.
  • 메모리 저장은 영속성을 줄여 노출 반경을 줄일 수 있지만, 새로고침 복원 전략이 필요합니다.
  • 어떤 저장 방식도 혼자서 모든 공격을 없애주지는 않습니다.
  • 실무에서는 access token은 메모리, refresh tokenHttpOnly 쿠키라는 절충안이 자주 나옵니다.

즉, 핵심은 "무조건 안전한 저장소"를 찾는 것이 아니라 위협 표면을 어떤 방향으로 줄일지 선택하는 것입니다.

먼저 XSSCSRF를 짧게 정리하면

XSS

Cross-Site Scripting은 공격자가 악성 스크립트를 우리 페이지 안에서 실행시키는 문제입니다.

이 상황이 위험한 이유는:

  • 페이지 안 자바스크립트가 할 수 있는 일을 공격자도 할 수 있기 때문입니다

즉, 스크립트가 읽을 수 있는 값은 공격자도 읽을 가능성이 커집니다.

CSRF

Cross-Site Request Forgery는 사용자가 의도하지 않은 요청을 다른 사이트나 경로를 통해 보내게 만드는 문제입니다.

이 상황이 위험한 이유는:

  • 브라우저가 인증 정보를 자동으로 같이 보낼 수 있기 때문입니다

즉, 자동 전송되는 인증 수단은 CSRF 관점을 함께 봐야 합니다.

짧게 줄이면:

  • XSS: "스크립트가 읽고 실행하는 문제"
  • CSRF: "브라우저가 자동으로 보내는 문제"

라고 이해하면 토큰 저장 전략이 훨씬 잘 보입니다.

왜 저장 위치가 곧 보안 모델일까?

토큰을 어디에 두느냐에 따라 달라지는 것이 많습니다.

  • 자바스크립트가 직접 읽을 수 있는가
  • 브라우저가 자동으로 전송하는가
  • 새로고침 뒤에도 남는가
  • 탭 종료 후 사라지는가
  • 외부 스크립트가 탈취하기 쉬운가

즉, 저장 위치는 단순 구현 디테일이 아니라 공격자가 토큰에 접근하는 경로를 어떻게 열고 닫을지 결정하는 문제입니다.

localStorage는 어떻게 봐야 할까?

가장 많이 비교되는 대상입니다.

장점부터 보면:

  • 사용이 단순하고
  • 새로고침 뒤에도 값이 남고
  • 디버깅이 쉽습니다

예를 들어:

localStorage.setItem('accessToken', token);

처럼 바로 저장할 수 있습니다.

하지만 보안 관점에서는 중요한 단점이 있습니다.

  • 자바스크립트에서 쉽게 읽을 수 있고
  • 따라서 XSS가 발생하면 토큰 탈취 표면이 커집니다

즉, localStorage의 핵심 리스크는 "브라우저 저장소라서 위험"이 아니라 스크립트 접근이 가능하다는 점입니다.

여기서 중요한 포인트는 이것입니다. XSS가 있으면 공격자는 꼭 토큰을 훔치지 않아도 사용자 권한으로 요청을 보낼 수 있습니다. 하지만 토큰이 localStorage에 있으면, 요청 악용을 넘어 토큰 자체를 외부로 유출해 장기 악용할 가능성까지 커질 수 있습니다.

즉, localStorage는 편하지만 민감한 장기 토큰 저장소로는 더 보수적으로 봐야 합니다.

sessionStorage는 더 안전할까?

처음에는 그렇게 느껴질 수 있습니다.

장점은:

  • 탭 단위로 유지되고
  • 브라우저를 완전히 닫으면 사라질 수 있으며
  • 영속성이 localStorage보다 약합니다

하지만 핵심 보안 특성은 크게 다르지 않습니다.

  • 여전히 자바스크립트에서 읽을 수 있고
  • 따라서 XSS에 노출되면 탈취될 수 있습니다

즉, sessionStorage지속성 측면의 차이는 있지만, XSS 관점의 본질적인 해결책은 아닙니다.

메모리 저장은 왜 자주 언급될까?

예를 들어 앱 상태 안에만 토큰을 두는 방식입니다.

let accessToken: string | null = null;

장점은 명확합니다.

  • 새로고침하면 사라지고
  • 영속 저장소에 직접 남지 않으며
  • 장기 노출 반경을 줄이기 쉽습니다

즉, XSS가 늦게 발생했을 때 이미 저장된 장기 토큰을 캐내가는 표면은 줄일 수 있습니다.

하지만 이것도 만능은 아닙니다.

  • 페이지에서 실행 중인 스크립트가 접근 가능한 상태라면
  • 현재 메모리에 있는 토큰은 여전히 악용될 수 있습니다

또한 UX 관점에서는:

  • 새로고침 후 복원 전략이 필요하고
  • 초기 진입 시 재발급 로직이 따로 필요합니다

즉, 메모리 저장은 보안상 장점이 있지만, 세션 복원과 토큰 재발급 설계가 같이 있어야 현실적인 전략이 됩니다.

쿠키는 왜 다르게 봐야 할까?

쿠키는 스토리지와 중요한 차이가 있습니다.

  • 브라우저가 자동으로 요청에 붙일 수 있고
  • 옵션에 따라 자바스크립트 직접 접근을 막을 수도 있습니다

특히 중요한 것은 HttpOnly입니다.

HttpOnly 쿠키의 의미

이 옵션이 붙은 쿠키는 자바스크립트에서 읽을 수 없습니다.

즉:

  • document.cookie로 접근할 수 없고
  • XSS가 있어도 토큰을 문자열로 빼내기 어려워집니다

이 지점 때문에 refresh token 저장 위치로 자주 검토됩니다.

그런데 왜 CSRF 얘기가 같이 나오나?

쿠키는 요청 시 브라우저가 자동 전송할 수 있기 때문입니다.

즉, 공격자가 사용자의 브라우저를 이용해 원치 않는 요청을 보내게 만들면, 쿠키도 따라갈 수 있습니다.

그래서 쿠키 전략은 보통 아래와 함께 가야 합니다.

  • SameSite
  • Secure
  • CSRF token
  • origin / referer 검증

즉, HttpOnly 쿠키는 XSS의 일부 표면을 줄이는 데 강하지만, 대신 자동 전송 모델에 맞는 CSRF 방어를 같이 설계해야 하는 방식입니다.

그래서 localStorage와 쿠키 중 무엇이 더 안전할까?

이 질문은 자주 나오지만, 한 줄짜리 정답은 없습니다.

대신 이렇게 보는 편이 더 정확합니다.

localStorage

  • 자동 전송되지 않음
  • CSRF 표면은 상대적으로 덜 직접적
  • 하지만 스크립트 접근 가능
  • XSS 시 토큰 유출 위험이 큼

HttpOnly 쿠키

  • 스크립트 직접 읽기 어려움
  • XSS에서 토큰 유출 표면을 줄일 수 있음
  • 하지만 자동 전송 모델이라 CSRF 방어가 필요

즉, 둘의 차이는:

  • localStorage: XSS 쪽 부담이 더 큼
  • 쿠키: CSRF 쪽 설계를 같이 해야 함

에 가깝습니다.

그래서 질문은 "무엇이 절대적으로 안전한가?"보다 우리 서비스에서 어느 위협을 어떤 방식으로 통제할 것인가? 가 더 맞습니다.

refresh token은 왜 HttpOnly 쿠키에 많이 두나?

실무에서 이 조합이 자주 나오는 이유는 비교적 명확합니다.

refresh token은:

  • 수명이 길고
  • 세션 연장 권한을 가지며
  • 탈취 시 피해가 커질 수 있습니다

그래서 이 토큰을 자바스크립트에서 직접 읽을 수 있는 저장소에 두는 것은 보수적으로 보게 됩니다.

반면 HttpOnly 쿠키에 두면:

  • 프론트엔드 코드가 직접 읽지 않아도 되고
  • 재발급 요청 시 브라우저가 자동으로 보낼 수 있으며
  • XSS에서 토큰 문자열 탈취 표면을 줄일 수 있습니다

즉, refresh token은 "자주 읽어야 하는 값"이 아니라 "재발급 시점에만 안전하게 보내지면 되는 값"이기 때문에 쿠키 전략과 잘 맞습니다.

access token은 왜 메모리에 두는 전략이 자주 나오나?

access token은 일반 API 요청마다 자주 필요합니다.

그래서 프론트엔드가 헤더에 붙여야 하는 구조라면 어느 정도 클라이언트가 들고 있어야 합니다.

이때 메모리 저장이 자주 언급되는 이유는:

  • 영속 저장소보다 노출 반경을 줄일 수 있고
  • 새로고침 시에는 refresh token 기반 복원으로 이어질 수 있기 때문입니다

즉:

  • access token: 짧게, 메모리
  • refresh token: 길게, HttpOnly 쿠키

라는 전략은 XSS와 CSRF 사이에서 자주 나오는 절충안입니다.

물론 이 방식도:

  • CSRF 대응이 필요하고
  • 재발급 엔드포인트 설계가 필요하며
  • 동시 요청 제어가 필요합니다

즉, 단순히 저장 위치만 바꾼다고 끝나는 문제는 아닙니다.

쿠키를 쓰면 XSS는 끝나는 걸까?

아닙니다. 이건 중요한 오해입니다.

HttpOnly 쿠키는 스크립트로 토큰 문자열을 읽기 어렵게 해줄 수 있습니다. 하지만 XSS가 발생하면 공격자는 여전히:

  • 사용자 화면에서 임의 요청을 보내고
  • 페이지 로직을 오염시키고
  • 민감한 UI 데이터를 읽고
  • 사용자의 권한으로 행동할 수 있습니다

즉, HttpOnly 쿠키는 XSS를 없애는 도구가 아니라 토큰 유출 표면을 줄이는 도구에 가깝습니다.

그래서 보안 관점에서는:

  • 입력 sanitization
  • React에서의 안전한 렌더링 습관
  • CSP
  • 서드파티 스크립트 관리

같은 XSS 자체를 줄이는 대책이 여전히 중요합니다.

Authorization 헤더를 쓰면 CSRF는 끝나는 걸까?

이것도 자주 생기는 단순화입니다.

브라우저가 자동으로 붙이는 쿠키와 달리, 헤더에 토큰을 직접 실어 보내면 전통적인 CSRF 표면은 줄어드는 경우가 많습니다. 하지만 실제 서비스는 더 복잡할 수 있습니다.

  • 어떤 요청은 여전히 쿠키에 의존할 수 있고
  • refresh endpoint는 쿠키 기반일 수 있고
  • 잘못된 CORS 정책이나 혼합 설계가 있으면 다른 문제가 생길 수 있습니다

즉, 헤더 기반이라고 해서 전체 보안 설계가 끝나는 것은 아닙니다. 다만 자동 전송 모델이 줄어든다는 점에서 차이가 있을 뿐입니다.

실무에서 자주 보이는 저장 전략

비교적 자주 보는 방향은 아래와 같습니다.

전략 1. 둘 다 localStorage

구현은 쉽지만, 장기 토큰까지 XSS 표면에 노출되기 쉬워 보수적으로 볼 필요가 있습니다.

전략 2. access token은 메모리, refresh tokenHttpOnly 쿠키

실무에서 많이 언급되는 절충안입니다.

  • 장기 토큰은 스크립트 접근을 줄이고
  • 짧은 토큰은 메모리에서 관리하고
  • 새로고침 후에는 재발급으로 복원합니다

전략 3. 전부 쿠키 중심

브라우저 중심 서비스에서 단순한 구조를 만들 수 있습니다. 대신 CSRF와 쿠키 옵션 설계가 중요합니다.

전략 4. 서버 세션 중심

프론트엔드가 토큰을 거의 직접 다루지 않는 모델입니다. SSR이나 전통적인 웹 서비스에서는 여전히 자연스럽습니다.

즉, 실무에서는 저장 위치 하나보다 인증 모델 전체와 위협 모델을 같이 보는 것이 중요합니다.

자주 하는 실수

정리하면 아래 실수가 정말 자주 나옵니다.

  • localStorage는 편하니 문제 없다고 생각한다
  • HttpOnly 쿠키면 XSS도 완전히 끝난다고 생각한다
  • 쿠키를 쓰면서 SameSite, Secure, CSRF 대응을 같이 보지 않는다
  • 메모리 저장 전략을 택했는데 새로고침 복원 흐름이 없다
  • refresh token 같은 장기 토큰을 스크립트 접근 가능한 곳에 둔다
  • 저장 위치만 바꾸면 전체 인증 보안이 해결된다고 생각한다

즉, 저장 전략은 단일 스위치가 아니라 공격 표면과 운영 복잡도의 교환입니다.

실무 체크리스트

실제로 설계할 때는 아래 질문으로 빠르게 점검하면 도움이 됩니다.

  1. 이 토큰은 자바스크립트가 반드시 직접 읽어야 하는가?
  2. 이 토큰이 탈취됐을 때 피해 반경은 얼마나 큰가?
  3. 이 저장 방식은 XSS에 어떤 표면을 열고 있는가?
  4. 이 저장 방식은 CSRF 방어를 별도로 요구하는가?
  5. 새로고침과 세션 복원은 어떻게 처리할 것인가?
  6. 장기 토큰과 단기 토큰을 같은 방식으로 저장하고 있지는 않은가?

이 질문에 답하고 나면 "어디에 저장할까?"도 단순 취향이 아니라 구조적인 설계 문제로 보이기 시작합니다.

정리하면

토큰 저장 전략을 한 줄로 줄이면, 인증 정보를 어디에 둘지 정하는 문제가 아니라 XSS와 CSRF 사이에서 어떤 위협 표면을 어떻게 통제할지 정하는 문제입니다.

실무 기준으로 기억할 핵심은 이렇습니다.

  • localStoragesessionStorage는 스크립트 접근 가능성이 핵심 리스크이고
  • HttpOnly 쿠키는 토큰 유출 표면을 줄일 수 있지만 CSRF 방어를 같이 봐야 하며
  • 메모리 저장은 영속 노출을 줄이는 대신 복원 전략이 필요하고
  • 장기 토큰일수록 더 보수적인 저장 전략이 필요합니다

결국 중요한 것은 "정답 저장소"를 찾는 것이 아니라, 우리가 어떤 위협을 더 크게 보고 어떤 운영 복잡도를 감수할 것인가를 명확히 하는 것입니다. 그 기준이 서야 localStorage, 쿠키, 메모리 전략도 각각 왜 쓰는지 더 분명해집니다.

같이 보면 좋은 글