📚 목차

    [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의 핵심은 **“사용자 브라우저가 모르는 사이에 인증 쿠키를 자동으로 보내버리는 것”**이다.

    • 예시:
      1. 사용자가 은행에 로그인해서 세션 쿠키를 보유
      2. 공격자가 만든 악성 사이트에 접속
      3. 그 사이트가 POST https://bank.com/transfer 요청 → 브라우저가 자동으로 쿠키 첨부
      4. 사용자가 의도치 않은 송금 발생 (CSRF 성공)

    → SameSite=Lax/Strict이면 cross-site 요청 시 쿠키가 자동 전송되지 않으니까, 공격자가 이런 식으로 세션을 이용하기 어려워짐.


    하지만 SameSite만으로는 부족한 이유

    1. SameSite=None을 꼭 써야 하는 경우 있음
      • 예: 소셜 로그인(OAuth) 리다이렉트, 서드파티 도메인에서 API 호출
      • 이땐 SameSite로 CSRF를 막을 수 없음
    2. 브라우저별 구현 차이 / 구버전 호환성
      • 오래된 브라우저는 SameSite 지원이 불완전
    3. 특정 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. 그래서 보통 어떤 전략을 쓰냐?

    👉 현실적으로 두 가지 패턴이 많다.

    1. 쿠키 기반 완전 세션 방식
      • Access + Refresh 모두 HttpOnly Secure Cookie
      • 서버에서 CSRF 방어를 꼭 챙겨야 함
      • 장점: 프론트 로직 단순, UX 부드러움
      • 단점: CSRF 공격 대비 설계 필요
    2. 하이브리드 방식(요즘 많이 씀)
      • Refresh Token → HttpOnly Secure Cookie
      • Access Token → 프론트 메모리(짧은 수명, 예: 15분)
      • 401 나면 Refresh로 갱신
      • 장점: XSS + CSRF 리스크를 균형 있게 줄임
      • 단점: 새로고침 시 무조건 Refresh 요청 한 번 필요

    4. 정리

    • Access Token을 쿠키에 넣는 것도 가능하고, 실제로 많이 쓴다.
    • 다만 그럴 경우 반드시 CSRF 방어를 해야 한다.
    • 반대로 메모리 보관은 CSRF엔 안전하지만, 새로고침 UX를 Refresh로 보완해야 한다.

    👉 요약하면:

    • 쿠키 저장: UX 부드럽지만 CSRF 방어 필수
    • 메모리 저장: 보안 더 깔끔하지만 새로고침 시 Refresh 과정 필요

    마무리

    프론트에서 브라우저 저장소를 고를 때는 “데이터 성격”과 “보안 위험”을 동시에 따져야 한다.

    • 단순 UI 상태 → localStorage
    • 탭 임시값 → sessionStorage
    • 대용량 캐시 → IndexedDB/Cache Storage
    • 인증 → AccessToken은 메모리 혹은 Cookie, RefreshToken은 HttpOnly Cookie
    Posted innetwork
    Written byEunwoo