Skip to main content

Command Palette

Search for a command to run...

쿠키 vs 로컬 스토리지 vs 세션 스토리지 — 언제, 왜 써야 할까?

Published
•4 min read•View as Markdown
D

프론트엔드 개발자 doit입니다.

웹 개발을 하다 보면 브라우저 저장소를 자주 쓰게 됩니다.

쿠키 / 로컬 스토리지 / 세션 스토리지 는 다 브라우저가 제공하는 “클라이언트 저장소”지만, 각 저장소마다 가지고 있는 특징 / 용량 / 보안성이 달라서 목적에 맞게 올바르게 선택해야 합니다.

1️⃣ 로컬 스토리지 (localStorage)

특징:

  • 데이터가 영구적으로 저장됩니다.

  • 사용자가 직접 지우지 않는 이상, 컴퓨터를 껐다 켜도 그대로 남아있습니다.

  • 용량: 도메인당 약 5MB 정도.

  • 자바스크립트로 접근 가능 (localStorage.getItem).

사용 예시:

  • 다크 모드 여부, 사용자 언어 설정

  • 최근 본 상품, 검색어 기록 등 캐시

  • 새로고침/재접속 후에도 남아야 하는 상태

2️⃣ 세션 스토리지 (sessionStorage)

특징:

  • 브라우저 탭 단위로 유지됩니다.

  • 탭을 닫으면 저장된 정보는 사라집니다.

  • 새로운 탭을 열 때 마다 모든 탭 각각에 새로운 sessionStorage 공간이 생깁니다.

  • 용량은 localStorage와 비슷, 약 5MB 정도.

사용 예시:

  • 다단계 회원가입 폼의 입력값 유지

  • 페이지 이동 시 필요한 임시 데이터

  • 뒤로 가기 했을 때 직전 상태 복원

⚠️ 각 스토리지들은 JS로 접근 가능해서 XSS 공격에 그대로 노출되기 때문에, 탈취됐을 때 문제가 생길만한 민감한 인증 토큰(JWT 등)을 넣는 건 권장되지 않습니다.

쿠키는 대부분 서버에 의해 관리됩니다.
쿠키란 브라우저가 서버에서 내려받아 자동으로 요청에 포함시켜주는 작은 데이터 조각
브라우저는 서버로부터 Set-Cookie 헤더를 받으면 내부 저장소에 기록해두고,
이후 요청마다 자동으로 Cookie 헤더에 포함해 서버로 다시 전송합니다.

Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Strict

보시다시피 쿠키에는 몇 가지 속성들을 가지고 있는데, 주요 속성들에 대해 하나하나 알아봅시다.

  • HttpOnly: 속성은 브라우저에게 쿠키를 클라이언트 코드에서 접근할 수 없게 하라는 명령입니다. 이 속성을 붙여주면 클라이언트 코드로 해당 쿠키에 접근이 불가능해지기 때문에 XSS에 대해 안전을 확보할 수 있습니다.

  • Secure: 속성은 연결이 HTTPS인 경우에만 해당 쿠키를 사용하라는 명령입니다. 일반 HTTP 요청시 해당 속성을 가진 쿠키는 전달되지 않습니다.

  • SameSite: 어떤 상황에서 쿠키를 자동으로 전송할지 제어 (CSRF 완화)

4️⃣ SameSite가 중요한 이유

웹 초창기 쿠키는 그냥 “자동 전송”만 있었습니다.

  • 사용자가 로그인하면 서버가 세션 쿠키(예: session=abc123)를 내려줍니다.

  • 브라우저는 이후 요청마다 이 쿠키를 자동으로 서버에 붙여 보내요 → 서버는 “아 이 사람 로그인한 그 사용자네” 하고 인식합니다.

그런데 문제는 외부 사이트 보낸 외부 요청에도 무조건 쿠키가 따라갔습니다.
그런데 이 특성이 CSRF 공격(다른 사이트에서 몰래 요청 보내서 내 쿠키 사용)이 핵심 취약점이었죠.
이를 막기 위해 도입된 게 SameSite 입니다. “외부 요청일 때는 쿠키를 제한하자” 라는 아이디어가 나오게 된 것이죠.

상황/옵션StrictLaxNone
같은 사이트 내 요청쿠키 전송 ✅쿠키 전송 ✅쿠키 전송 ✅
외부 사이트에서 시작된 GET (링크 클릭, 주소창 입력 등)전송 ❌전송 ✅전송 ✅
외부에서 시작된 POST/PUT/DELETE/AJAX전송 ❌전송 ❌전송 ✅

CSRF와 SameSite의 관계

  • CSRF: 해커 사이트가 내 브라우저의 로그인 쿠키를 ‘몰래’ 이용해 타 사이트에 요청시키는 공격.

  • Strict: 외부에서 시작된 요청에는 쿠키를 절대 안 붙임 → CSRF 사실상 원천 차단.

  • Lax: 외부 GET은 허용하지만, 외부 POST 등은 쿠키 차단 → 대부분의 CSRF 시나리오 완화.

  • None: 제약 없음 → 반드시 CSRF 토큰 등 다른 방어가 필요.

👉 브라우저는 요청의 출발지(site)를 기준으로 같은 사이트인지 판단합니다.

  • 내가 google.com에서 shop.com 링크를 클릭 → 외부 사이트에서 시작된 요청

  • 내가 shop.com 내부에서 페이지 이동 → 같은 사이트 요청

“도착지가 shop.com이어도 출발지가 shop.com이 아니면 외부로 본다”가 핵심.

5️⃣ 무엇을 선택할까?

B2C(쇼핑몰, 커뮤니티 등)

  • 외부 유입(검색/SNS/광고) 많음

  • 사용자는 링크 눌러 들어와도 로그인 유지되길 기대

  • 추천: SameSite=Lax + CSRF 토큰 + HttpOnly + Secure

    • 이유: 외부 링크 GET에는 쿠키가 붙어 로그인 유지(UX), 외부 POST는 막혀 CSRF 완화

B2B(내부 시스템: PMS/ERP/CRM 등)

  • 외부 유입 거의 없음, 보안 우선

  • 추천: SameSite=Strict + HttpOnly + Secure (+ 필요 시 MFA/2FA)

    • 이유: 외부에서 시작된 모든 요청 차단 → CSRF 강력 봉쇄

    • 링크로 들어와 로그인 풀리는 UX 이슈? 내부 시스템에선 영향 작음

외부 파트너사도 접근하는 B2B 포털처럼 외부 링크 접근이 꽤 있는 경우엔

Lax + CSRF 토큰 조합이 현실적입니다.

참고: SameSite는 2016년 Chrome 51에서 처음 도입되었고, 2020년부터는 기본값이 Lax 로 바뀌었습니다. 즉, 명시하지 않아도 기본적으로 Lax 동작을 하지만, 예전 브라우저는 기본값이 None처럼 동작할 수 있습니다. 또한 서비스 정책을 명확히 하기 위해 현업에서는 SameSite 를 항상 명시적으로 설정하는 것이 권장됩니다.

6️⃣ 함께 쓰는 보안 옵션

  • HttpOnly: 자바스크립트로 쿠키 접근 불가 → XSS로 쿠키 탈취 어렵게

  • Secure: HTTPS에서만 전송 → 중간자 공격 완화

  • CSRF 토큰: 폼/비동기 요청에 서버가 발급한 토큰을 함께 제출하도록 강제

  • Origin/Referer 검사: 요청의 출발지 헤더로 2차 확인

  • 짧은 세션 수명 + 재발급(회전), 로그아웃 시 서버 세션 폐기

기억하기: SameSite는 CSRF의 “자동 쿠키 전송”을 줄여주는 1차 안전장치이고,

CSRF 토큰/Origin 체크는 서버에서 최종적으로 검증하는 2차 안전장치입니다.

정리

  • localStorage / sessionStorage

    • 장점: CSRF와는 무관 (자동 전송 안 되니까)

    • 단점: JS에서 접근 가능 → XSS 뚫리면 토큰 그대로 털림

    • 현실적으로 XSS는 “사람이 실수하기 쉬운 부분”이라 더 위험

  • 쿠키 (HttpOnly + Secure + SameSite 설정)

    • 장점: HttpOnly 덕분에 JS에서 접근 불가 → XSS에 훨씬 안전

    • 단점: 자동 전송 특성 때문에 CSRF 위험, 하지만HttpOnly + Secure + SameSite 설정으로 방어 가능

CSRF는 브라우저/서버 설정 몇 가지로 방어선 구축 가능
XSS는 코드 전반에서 조심해야하고, 한번 뚫리면 localStorage/sessionStorage에 있는 토큰은 그대로 털림따라서 보안 민감 서비스(은행, 결제, 기업 내부 시스템)에서는 XSS에 강한 HttpOnly 쿠키가 더 안전하다고 보는 거예요

👉 그래서 실무에서는 보안 민감 서비스(은행, 결제, 기업 내부 시스템) → 쿠키 세션
👉 API 서버와 SPA 같은 구조 → JWT + 로컬 스토리지를 많이 쓰지만, 요즘은 JWT도 쿠키(HttpOnly)로 관리하는 흐름이 늘어나는 중이에요.

7 views

More from this blog

Agent Skill로 README 자동화하기

저는 요즘 업무에서 AI 에이전트를 적극적으로 활용하여 반복적인 업무를 최대한 AI로 자동화하기 위해 공부 중인데요. 이번에 했던 작업 중 하나가 README 파일 생성 자동화입니다. AI에게 README를 써달라고 하면 꽤 그럴싸한 결과물이 나옵니다. 하지만 여러 프로젝트에 반복하다 보면 프롬프트가 모호할 때마다 매번 다른 방식으로 문제를 풀기 때문에 들

Mar 31, 20268 min read8

배열을 처리하는 세 가지 방법 (chaining, reduce, for loop)

개발을 하다 보면 배열 데이터를 가공할 일이 정말 많은데요.저는 평소에는 filter, map 같은 메서드 체이닝 방식을 주로 사용합니다. 읽기 쉽고, 데이터 흐름이 단계별로 명확하게 보이기 때문이죠. 하지만 어떠한 계기로(?) 인해 이런 의문을 가지게 되었습니다.. "만약 데이터가 수천만 건으로 늘어난다면? 배열을 여러 번 순회하는 체이닝 방식이 성능 병목을 일으키지는 않을까?" 혹은 "reduce 하나로 합치는 게 베스트일까?" 이 질문을 ...

Feb 4, 20263 min read19

[React] 좋은 추상화란 무엇인가(2) 잘못된 추상화는 중복보다 비싸다

지난 글을 보면 성급했던 재사용 컴포넌트 경험으로 고생을 한 적이 있었는데요.(아직 안 보셨다면? [1편: 좋은 추상화란 무엇인가(1)?] ) 애초에 왜 이런 고생을 했을까를 생각해 보면이유는 단순했습니다. 중복 코드가 거슬리니 바로 추상화를 해버린 것이었죠..똑같은 실수를 반복하지 않기 위해 저만의 기준이 필요하다고 느꼈습니다. 이에 대해 이미 깊이 고민하셨던 Cher Scarlett님과 Kent C. Dodds님의 글을 보면서, 제가 취해야...

Dec 16, 20253 min read25

[React] 좋은 추상화란 무엇인가(1) ? 유연한 컴포넌트 설계를 위한 제어의 역전

재사용을 위해 만든 컴포넌트가, 시간이 지날수록 오히려 재사용하기 어려워진 경험이 있으신가요? 저는 최근 프로젝트에서 SearchFilter 컴포넌트가 그러한데요 프로젝트에는 여러 목록 페이지가 존재했고, 페이지마다 검색 조건이 제각각이었습니다. 그래서 처음에는 Configuration 배열만 넘기면 알아서 그려주는 컴포넌트를 만들었습니다. // ❌ 초기의 설정 기반 접근 (Configuration) const filterFields = [ ...

Dec 9, 202510 min read36

React 시대의 함수형 프로그래밍

바닐라 JS처럼 개발자가 DOM과 상태 변경을 직접 제어하던 시기에는 불변성·순수 함수 같은 함수형 프로그래밍 철학이 지금처럼 중요하지는 않았습니다. 하지만 React(Vue/Redux/SWR/Recoil 등)처럼 선언적·상태 기반 UI가 등장하면서 상태를 “데이터 흐름의 스냅샷”으로 다뤄야 했고, 그때부터 함수형 프로그래밍이 중요해지기 시작했습니다. 🔥 바닐라 JS에서는 왜 FP가 별로 중요하지 않았을까? 바닐라 JS 시대의 UI 개발은...

Dec 8, 20255 min read35
D

Dlog!

39 posts

배우고, 나누며, 함께 성장하는 개발자 🚀