Browser Storage
🧩 Reference
쿠키 (Cookie)
- 서버가 브라우저에 사용자에 대한 작은 데이터 조각을 저장해두는 방식.
- 브라우저는 이후 같은 도메인으로 요청을 보낼 때 이 쿠키를 자동으로 함께 전송한다.
세션 (Session)
- 서버가 메모리에 이 사용자는 누구인지에 대한 상태(세션 데이터)를 들고 있고,
- 브라우저에는 그 세션을 가리키는 세션 ID만 쿠키로 저장하는 방식의 인증 모델.
토큰 (Token)
- 서버가 발급하는 “서명된 인증 정보 뭉치”.
- 보통 이 토큰을 들고 다니면서, 매 요청마다 Authorization 헤더 등에 직접 실어 보내 인증한다.
JWT (JSON Web Token)
- 토큰 포맷의 한 종류로,
header.payload.signature 구조를 가진 JSON 기반 서명 토큰. - payload 안에 유저 ID, 만료 시간 같은 정보를 담고,
서버는 이 토큰이 위조되지 않았는지만 검증하고 세션은 따로 들고 있지 않아도 된다.
🧩 아래 정리를 하고 깨달은 점!
- RefreshToken을 왜 localStorage에 저장하면 위험한지
→ localStorage는 JS로 읽혀서 XSS에 그대로 털리기 때문. - httpOnly 쿠키가 왜 XSS에 안전한지
→ JS 접근이 불가능해 스크립트가 떠도 쿠키 값을 읽을 수 없기 때문. - localStorage vs sessionStorage vs memory
→ 모두 JS 접근 가능하지만, 보존 기간·범위가 달라서 용도에 맞게 선택해야 한다. - AccessToken을 왜 Authorization 헤더에 실어야 하는지 → 쿠키 자동 전송을 피하고, 명시적으로 인증 정보를 보내 CSRF 위험을 줄이기 위해서.
- JWT 인증은 HTTP/HTTPS 규약 위에서 움직인다는 점
→ 토큰 전달·쿠키 저장·401 응답·재발급 흐름은 모두 HTTP 프로토콜 규칙에 따른 동작이다.
웹 인증을 이해하려면, 먼저 브라우저가 어떤 저장소들을 제공하는지 확실히 알아야 한다!
브라우저에는 크게 네 가지 저장소가 있다:
- Memory (JS 메모리)
- LocalStorage
- SessionStorage
- Cookie
Memory (애플리케이션 메모리)
메모리란 브라우저 탭이 열려 있는 동안, JS 런타임이 들고 있는 휘발성 데이터 영역이다.
React state, Zustand, context 등에 저장되는 모든 데이터는 메모리에 있다.
특징
- 새로고침하면 사라진다.
- XSS에 취약하다. (JS가 읽을 수 있으므로)
- 속도는 가장 빠르다.
- 브라우저 탭을 닫으면 사라진다.
- 서버로 자동 전송되지 않는다. (쿠키와 다름)
JWT에서 어떻게 쓸까?
AccessToken을 메모리에 넣는 서비스가 많다.
- AT는 짧게 살아서 털려도 피해 작다.
- JS에서 관리해도 된다.
- 자동 전송되지 않아 CSRF 위험이 없다.
- 새로고침해도 RefreshToken이 쿠키에 있으니 재발급하면 된다.
즉, AccessToken은 메모리에 저장해도 괜찮다.
LocalStorage
도메인별로 키-값 쌍을 영구적으로 저장하는 브라우저 내장 저장소이다.
브라우저를 꺼도 남아 있고, JS에서 localStorage API로 읽고 쓸 수 있다.
특징
- 브라우저를 꺼도 유지된다.(영구 저장)
- 저장할 수 있는 용량이 크다.
- JS로 접근 가능 → XSS 공격에 의해 값이 그대로 유출될 수 있다.
- 서버로 자동 전송되지 않음 (쿠키와 다름)
언제 사용할까?
- 사용자 설정(테마, 언어, UI 상태)
- 로그인 여부(Boolean만)
- 간단한 캐시 등
JWT에서는?
AccessToken을 여기에 저장하는 서비스도 있지만 보안 관점에서는 비추천이다.
- XSS 공격에서 localStorage는 바로 털린다. = AccessToken 탈취
- 매우 위험하지는 않지만(AT만 털리면 재발급 가능하니까) 그래도 좋지는 않다.
추가로, localStorage는 브라우저를 꺼도 계속 남아 있는 영구 저장소라서,
원래 짧게 쓰고 버려야 할 AccessToken이 불필요하게 오래 살아남는다는 점도 위험 요소다.
→ 토큰이 유효 기간 동안 브라우저에 계속 남아 있으니, 탈취 가능 기간도 함께 길어진다.
💡
AccessToken = localStorage 가능하나 XSS 취약
RefreshToken = 절대 localStorage 금지
SessionStorage
localStorage와 비슷하지만 브라우저 탭(세션) 단위로 유지되는 키-값 저장소이다.
구조는 localStorage와 같지만, 탭을 닫으면 함께 사라진다.
특징
- 탭 닫으면 날아간다.
- 새로고침해도 유지된다.
- JS 접근 가능 → XSS 취약하다.
JWT에서 어떻게 쓸까?
JWT에서는 sessionStorage 거의 쓰지 않는다.
Cookie
HTTP 요청/응답 헤더를 통해 서버와 브라우저가 주고받는 작은 텍스트 조각이다.
브라우저가 자동으로 저장·관리하며, 같은 도메인으로 요청을 보낼 때 Cookie 헤더에 자동으로 실어 보낸다.
쿠키는 브라우저가 서버 말을 듣고 직접 관리하므로 프론트에서 쿠키 저장을 하지 않아도 된다.
서버가 쿠키를 저장하는 과정
Set-Cookie: refresh_token=abc123; HttpOnly; Secure; SameSite=Strict이렇게 서버가 응답을 내려주면:
- 브라우저가 쿠키를 자동으로 저장한다.
- 같은 도메인으로 요청을 보낼 때 자동으로 Cookie 헤더에 실려 나간다.
Cookie: refresh_token=abc123;쿠키 속성들
| 속성 | 값 | 설명 | 예시 |
|---|---|---|---|
| httpOnly | true/false | JS 접근 차단 → XSS 방어 | HttpOnly |
| Secure | true/false | HTTPS에서만 전송 | Secure |
| SameSite | Strict/Lax/None | CSRF 방지 수준 조절 | SameSite=Strict |
| Domain | 도메인명 | 쿠키 적용 도메인 범위 | Domain=.example.com |
| Path | 경로 | 쿠키 적용 경로 범위 | Path=/api |
| Max-Age | 초 | 쿠키 수명 (초 단위) | Max-Age=604800 (7일) |
| Expires | 날짜 | 쿠키 만료 절대 시간 | Expires=Wed, 21 Oct 2025 |
특히 Path를 /auth 나 /api/auth처럼 최대한 좁게 설정하면,
쿠키가 전송되는 요청 범위를 줄일 수 있어서 공격 표면을 줄이는 데 도움이 된다.
SameSite 상세:
Strict: 완전 동일 사이트만 → 가장 안전, UX 불편Lax: GET + 탑레벨 네비게이션 허용 → 기본값, 균형적None: 모든 크로스사이트 허용 → Secure 필수
일반 쿠키 vs httpOnly 쿠키 비교
| 구분 | Cookie (일반) | Cookie (httpOnly) |
|---|---|---|
| JS 접근 가능 여부 | O (document.cookie) | X (JS 접근 불가) |
| XSS 공격 | 매우 취약 | 안전 |
| CSRF 위험 | 동일 (둘 다 자동 전송) | 동일 |
| JWT에 적합? | 부적합 | RefreshToken 저장 표준 방식 |
| 서버 제어 가능? | O | O |
왜 RefreshToken은 쿠키여야 할까?
1. 기본적으로 자동 전송이 필요하다.
RefreshToken은 서버가 클라이언트 신원을 확인하는 용도다.
이걸 프론트에서 직접 꺼내서 붙이는 구조로 만들면 안된다.
쿠키는 자동 저장, 자동 전송, JS 접근 불가
이 특징 때문에 RefreshToken을 넣기 최적화된 저장 공간이다.
2. XSS로부터 보호해야 한다.
localStorage / sessionStorage / 메모리는 JS로 접근 가능 → XSS 공격에 취약
httpOnly 쿠키는 JS로 읽을 수 없다.
3. RefreshToken 탈취 = 계정 영구 장악
RT가 재발급 토큰이므로 털리면 세션 영구 장악이다.
→ RT는 절대 노출되면 안 되므로 httpOnly + Secure + SameSite 쿠키를 사용.
Memory vs LocalStorage 저장 전략
AccessToken 저장 위치 선택 기준:
| 기준 | Memory | LocalStorage |
|---|---|---|
| 보안(XSS) | 위험 | 매우 위험 |
| CSRF | 안전 | 안전 |
| 새로고침 | 사라짐 | 유지됨 |
| UX 편의성 | 낮음 | 높음 |
| 권장 여부 | 권장 | 조건부 가능 |
정리 :
- 최대 보안 = Memory
- 편의성 중시 = LocalStorage
세션 기반 인증 vs JWT 인증
| 구분 | 세션 기반 | JWT 기반 |
|---|---|---|
| 서버 저장소 | 세션 저장소 필요 | 무상태(stateless) |
| 확장성 | 낮음 | 높음 (분산 서버에서 좋음) |
| 토큰 크기 | 작음 (세션 ID) | 큼 (header+payload+signature) |
| RT 필요 여부 | 필요 없음 | 필요 (재발급용) |
| 보안 수준 | 높음 | 구현에 따라 달라짐 |
브라우저 저장소 용량 제한
| 저장소 | 용량 제한 | 추가 제한사항 |
|---|---|---|
| Memory | 브라우저 메모리에 따라 | 탭 닫으면 사라짐 |
| LocalStorage | 5-10MB (브라우저별 상이) | 도메인당 제한 |
| SessionStorage | 5-10MB | 탭별 독립적 |
| Cookie | 4KB/개, 50개/도메인 | 모든 요청에 자동 전송 |
JWT RefreshToken 주의점:
- JWT가 길면 쿠키 4KB 초과 가능
- 이때는 JWT 대신 세션 ID 방식 고려
정리
| 구분 | Memory | LocalStorage | SessionStorage | Cookie (httpOnly) |
|---|---|---|---|---|
| 유지 기간 | X (탭 종료 시 삭제) | O (브라우저 꺼도 유지) | X (탭 닫으면 삭제) | O (만료 시간까지 유지) |
| 새로고침 영향 | X (사라짐) | O (유지됨) | O (유지됨) | O (유지됨) |
| 브라우저 자동 전송 | X | X | X | O (Cookie 헤더로 자동 전송) |
| JS 접근 가능 여부 | O (접근 가능 → XSS 취약) | O (접근 가능 → XSS 매우 취약) | O (접근 가능 → XSS 취약) | X (접근 불가 → XSS 안전) |
| 사용 용도 | AccessToken, 앱 상태 | 사용자 설정, 캐시, UI 상태 | 탭 기반 임시 데이터 | RefreshToken, 인증 유지 |
| 보안 위협 모델 | XSS 취약 | XSS 매우 취약 | XSS 취약 | XSS 안전 / CSRF 고려 필요 |
| 서버가 통제 가능? | X | X | X | O (서버가 Set-Cookie로 관리) |
| 적합한 JWT 역할 | AccessToken(추천) | AccessToken(조건부) | 거의 사용 X | RefreshToken(표준) |
💡 저장소 선택 기준 요약
JWT 인증에서 저장소를 선택할 때 핵심 기준은 다음 네 가지다:
XSS 보호가 필요한가?
→ RefreshToken처럼 절대 노출되면 안 되는 값은 httpOnly Cookie자동 전송이 필요한가?
→ 서버가 알아서 확인해야 하는 값은 Cookie새로고침해도 유지해야 하나?
→ 오래 살아야 하는 값은 LocalStorage,
→ 짧게만 유지해도 되는 값은 MemoryCSRF 보호가 필요한가?
→ Cookie 쓰면 SameSite/CSRF 토큰을 반드시 고려해야 함
결론:
- AccessToken → Memory(권장) / LocalStorage(조건부)
- RefreshToken → 반드시 httpOnly Cookie