📚 목차
[Network] 브라우저 저장소 차이점
ByEunwoo
network프론트엔드에서 개발하다 보면 데이터를 어디에 저장할지 고민하게 된다.
“사용자 테마를 기억해야 할까?”, “장바구니를 브라우저에 임시 저장할까?”, “토큰은 어디 두는 게 안전하지?” 같은 고민 말이다.
브라우저는 여러 종류의 저장소를 제공하는데, 각각 특징과 보안 이슈가 달라서 상황에 맞는 선택이 중요하다.
1. LocalStorage
- 특징: 브라우저를 꺼도 데이터가 남는다. 사용자가 직접 지우거나
clear()
하지 않는 이상 영구 저장. - 용량: 대략 5MB 정도 (브라우저별 차이 있음)
- 접근법:
localStorage.setItem
,getItem
,removeItem
- 사용처: 테마 모드, 비로그인 상태 유지, 가벼운 캐시
// 저장
localStorage.setItem('theme', 'dark');
// 읽기
const theme = localStorage.getItem('theme');
// 삭제
localStorage.removeItem('theme');
- 오직 문자열만 저장 가능 → 객체는
JSON.stringify
/JSON.parse
필요 - XSS 공격에 토큰이나 민감 데이터가 노출될 수 있으니, 액세스 토큰 저장은 절대 금지
2. SessionStorage
- 특징: 탭 단위 저장소. 브라우저 탭을 닫으면 자동으로 삭제된다.
- 용량: LocalStorage와 비슷 (~5MB)
- 사용처: 입력 폼 임시 저장, 결제 과정 중간 값, 비로그인 장바구니
sessionStorage.setItem('draft', JSON.stringify({ title: '임시 저장' }));
const draft = JSON.parse(sessionStorage.getItem('draft'));
⚠️ 주의
- localStorage와 동일하게 XSS에 취약
- 다른 탭과 공유되지 않으므로 탭마다 독립적인 값이 필요할 때만 적합
3. Cookie
- 특징: 서버가
Set-Cookie
헤더로 심어주면, 브라우저가 같은 도메인 요청 시 자동으로 첨부한다. - 용량: 4KB 내외 (작다)
- 강점:
HttpOnly
,Secure
,SameSite
같은 속성으로 보안을 강화할 수 있다. - 사용처: 세션 ID, Refresh Token
Set-Cookie: refreshToken=xxx;
HttpOnly; Secure; SameSite=Lax; Max-Age=2592000
⚠️ 주의
- 자동 전송 특성 때문에 CSRF 공격에 취약 →
SameSite
옵션 필수 - 용량이 작아서 설정값 저장에는 적합하지 않다
보안 관점 핵심
- XSS (교차 스크립트 공격)
localStorage, sessionStorage, IndexedDB는 JS로 접근 가능 → 공격자가 스크립트를 삽입하면 토큰 유출 - CSRF (사이트 간 요청 위조)
쿠키는 자동 전송되므로 의도치 않은 요청 가능 →SameSite
, CSRF 토큰 필요 - 세션 하이재킹
탈취된 토큰으로 인증 사칭 가능 → Access Token은 짧게, Refresh Token은 HttpOnly 쿠키에 저장
프론트에서 토큰 보관 전략
권장 패턴
- Access Token: 메모리 저장 (짧은 만료, 새로고침 시 사라짐)
- Refresh Token: HttpOnly 쿠키 (Secure + SameSite 옵션 적용)
- 요청 실패(401) →
/auth/refresh
호출 → 새 Access Token 받아서 다시 요청
피해야 할 패턴
- localStorage/sessionStorage에 Access/Refresh Token 저장 ❌ (XSS 취약)
4. IndexedDB
- 특징: 브라우저 내장 NoSQL DB. JSON 객체, Blob, 이미지 등 복잡한 데이터도 저장 가능
- 용량: 수십 MB~수백 MB까지 (브라우저·디스크 상태 따라 달라짐)
- 사용처: 오프라인 캐시, 대규모 데이터 저장, 이미지/지도 타일 저장
const request = indexedDB.open('myDB', 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (e) => {
const db = e.target.result;
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').add({ id: 1, name: 'Alice' });
};
⚠️ 주의
- API가 비동기라 코드가 장황 → 보통
idb
같은 래퍼 라이브러리 사용 - 버전 관리, 마이그레이션 필요
5. Cache Storage (Service Worker)
- 특징: 요청-응답을 그대로 캐시할 수 있다.
- 사용처: 오프라인 PWA, 정적 리소스 캐싱
- 주의: 인증 필요한 API 응답은 캐시에 넣으면 안 된다(사용자 간 데이터 섞일 위험)
AccessToken을 메모리야 저장하는게 옳을까?에 대한 생각 정리
1. Access Token을 쿠키에 저장하는 경우
- 방식: 서버에서
Set-Cookie
헤더로 Access Token을 내려주고, 이후 모든 요청에 쿠키 자동 전송 - 장점
- 프론트에서 따로 토큰을 관리할 필요 없음 (헤더에 넣을 필요 없이 쿠키 자동 첨부)
- 새로고침/탭 닫기 후에도 쿠키가 살아 있으면 계속 유효 → UX 좋음
- 단점
- CSRF에 취약 (쿠키는 자동 전송되기 때문)
- 방어:
SameSite=Lax/Strict
+ CSRF 토큰 추가 검증
SameSite가 CSRF를 막는 방식
CSRF의 핵심은 **“사용자 브라우저가 모르는 사이에 인증 쿠키를 자동으로 보내버리는 것”**이다.
- 예시:
- 사용자가 은행에 로그인해서 세션 쿠키를 보유
- 공격자가 만든 악성 사이트에 접속
- 그 사이트가
POST https://bank.com/transfer
요청 → 브라우저가 자동으로 쿠키 첨부 - 사용자가 의도치 않은 송금 발생 (CSRF 성공)
→ SameSite=Lax/Strict이면 cross-site 요청 시 쿠키가 자동 전송되지 않으니까, 공격자가 이런 식으로 세션을 이용하기 어려워짐.
하지만 SameSite만으로는 부족한 이유
- SameSite=None을 꼭 써야 하는 경우 있음
- 예: 소셜 로그인(OAuth) 리다이렉트, 서드파티 도메인에서 API 호출
- 이땐 SameSite로 CSRF를 막을 수 없음
- 브라우저별 구현 차이 / 구버전 호환성
- 오래된 브라우저는 SameSite 지원이 불완전
- 특정 CSRF 기법은 여전히 가능
- 예: 브라우저 버그, 플러그인, 내부망 공격 같은 특수 케이스
그래서 보통 이렇게 씀
- 기본 방어선:
SameSite=Lax
(대부분 CSRF 막음, UX도 괜찮음) - 추가 방어선: CSRF 토큰 (Synchronizer Token / Double Submit Cookie)
- 프론트에서
X-CSRF-Token
같은 헤더에 토큰 실어 보내고, 서버가 비교
- 프론트에서
- 쿠키 설정: 항상
Secure
(HTTPS) +HttpOnly
도 같이
2. Access Token을 메모리에만 저장하는 경우
- 방식: 로그인 시 응답에서 Access Token을 받고, 프론트 메모리(변수/Zustand/Redux 등)에만 보관
- 장점
- XSS 이슈는 여전히 있지만, CSRF 위험은 없다 (자동 전송이 안 되므로)
- 토큰이 새로고침 시 사라져도 Refresh Token으로 재발급 가능 → 더 안전
- 단점
- 새로고침하면 토큰이 날아가므로, 항상 Refresh 요청을 한 번 거쳐야 함 → UX에서 약간의 지연 발생
3. 그래서 보통 어떤 전략을 쓰냐?
👉 현실적으로 두 가지 패턴이 많다.
- 쿠키 기반 완전 세션 방식
- Access + Refresh 모두
HttpOnly Secure Cookie
- 서버에서 CSRF 방어를 꼭 챙겨야 함
- 장점: 프론트 로직 단순, UX 부드러움
- 단점: CSRF 공격 대비 설계 필요
- Access + Refresh 모두
- 하이브리드 방식(요즘 많이 씀)
- Refresh Token →
HttpOnly Secure Cookie
- Access Token → 프론트 메모리(짧은 수명, 예: 15분)
- 401 나면 Refresh로 갱신
- 장점: XSS + CSRF 리스크를 균형 있게 줄임
- 단점: 새로고침 시 무조건 Refresh 요청 한 번 필요
- Refresh Token →
4. 정리
- Access Token을 쿠키에 넣는 것도 가능하고, 실제로 많이 쓴다.
- 다만 그럴 경우 반드시 CSRF 방어를 해야 한다.
- 반대로 메모리 보관은 CSRF엔 안전하지만, 새로고침 UX를 Refresh로 보완해야 한다.
👉 요약하면:
- 쿠키 저장: UX 부드럽지만 CSRF 방어 필수
- 메모리 저장: 보안 더 깔끔하지만 새로고침 시 Refresh 과정 필요
마무리
프론트에서 브라우저 저장소를 고를 때는 “데이터 성격”과 “보안 위험”을 동시에 따져야 한다.
- 단순 UI 상태 → localStorage
- 탭 임시값 → sessionStorage
- 대용량 캐시 → IndexedDB/Cache Storage
- 인증 → AccessToken은 메모리 혹은 Cookie, RefreshToken은 HttpOnly Cookie