쿠키와 localStorage 차이
브라우저에 값을 저장해야 할 때 cookie와 localStorage를 자주 만난다.
둘 다 사용자의 브라우저에 데이터를 남길 수 있지만, 쓰임새는 다르다.
가장 큰 차이는 서버로 자동 전송되는지다.
쿠키는 조건이 맞으면 HTTP 요청에 자동으로 포함된다.localStorage는 브라우저 안에만 저장되고, 서버로 보내려면 JavaScript 코드에서 직접 꺼내 요청에 실어야 한다.
그래서 로그인 세션처럼 서버가 매 요청에서 확인해야 하는 값은 쿠키가 자주 쓰인다.
반대로 화면 설정, 임시 선택값, 닫아도 유지할 UI 상태처럼 클라이언트에서만 필요한 값은 localStorage가 더 단순하다.
쿠키의 역할
쿠키는 서버가 Set-Cookie 응답 헤더로 브라우저에 저장하라고 지시할 수 있는 작은 데이터다.
브라우저는 저장한 쿠키를 이후 조건에 맞는 요청에 Cookie 요청 헤더로 다시 보낸다.
HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax이후 같은 사이트로 요청을 보낼 때 브라우저가 쿠키를 함께 보낼 수 있다.
GET /mypage HTTP/1.1
Host: example.com
Cookie: sessionId=abc123이 흐름 때문에 쿠키는 서버와 함께 쓰기 좋다.
서버는 요청마다 쿠키를 보고 사용자를 식별하거나, 장바구니와 같은 상태를 이어갈 수 있다.
쿠키는 Expires나 Max-Age로 만료 시간을 지정할 수 있다.
만료 시간이 없으면 보통 세션 쿠키로 취급되고, 브라우저 세션이 끝날 때 삭제될 수 있다.
Set-Cookie: theme=dark; Max-Age=2592000; Path=/단, 브라우저 세션 복원 기능 같은 동작 때문에 세션 쿠키의 실제 삭제 시점은 사용자가 생각한 것과 다를 수 있다.
중요한 값이라면 만료 정책을 서버에서 명확히 다루는 편이 좋다.
localStorage의 역할
localStorage는 Web Storage API의 일부다.
같은 origin의 문서에서 접근할 수 있는 key-value 저장소를 제공한다.
localStorage.setItem('theme', 'dark')
const theme = localStorage.getItem('theme')localStorage에 저장한 값은 브라우저를 닫았다가 다시 열어도 유지된다.
MDN 문서 기준으로 sessionStorage는 탭 세션이 끝나면 사라지지만, localStorage는 브라우저를 닫고 다시 열어도 유지된다.
저장되는 key와 value는 문자열이다.
객체나 배열을 저장하려면 JSON.stringify()로 문자열로 바꾸고, 꺼낼 때 JSON.parse()로 되돌리는 패턴을 쓴다.
const userSetting = {
theme: 'dark',
compact: true,
}
localStorage.setItem('userSetting', JSON.stringify(userSetting))
const saved = localStorage.getItem('userSetting')
const parsed = saved ? JSON.parse(saved) : nulllocalStorage는 서버로 자동 전송되지 않는다.
API 요청에 포함하고 싶다면 직접 값을 꺼내서 header나 body에 넣어야 한다.
const token = localStorage.getItem('accessToken')
await fetch('/api/me', {
headers: {
Authorization: `Bearer ${token}`,
},
})이 점은 장점이기도 하고 단점이기도 하다.
불필요한 요청마다 값이 실리지 않는 것은 장점이다.
하지만 서버가 자동으로 세션을 판단해야 하는 구조라면 직접 처리해야 할 코드가 늘어난다.
자동 전송 차이
쿠키는 요청에 자동으로 포함될 수 있다.
브라우저가 Domain, Path, Secure, SameSite 같은 속성을 보고 보낼지 결정한다.
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax이 구조에서는 프론트엔드 코드가 매번 sessionId를 직접 꺼내지 않아도 된다.
서버는 요청 헤더에 들어온 쿠키를 확인하면 된다.
localStorage는 자동으로 요청에 포함되지 않는다.
아래처럼 저장만 해두면 서버는 이 값을 알 수 없다.
localStorage.setItem('sessionId', 'abc123')서버로 보내려면 직접 코드를 작성해야 한다.
const sessionId = localStorage.getItem('sessionId')
await fetch('/api/me', {
headers: {
'X-Session-Id': sessionId ?? '',
},
})그래서 “서버가 매 요청에서 자동으로 받아야 하는 값”이면 쿠키가 자연스럽다.
“화면에서만 쓰고 서버 요청마다 보낼 필요가 없는 값”이면 localStorage가 더 가볍다.
JavaScript 접근 차이
localStorage는 JavaScript에서 접근하는 저장소다.
코드에서 getItem(), setItem(), removeItem()으로 값을 읽고 쓴다.
localStorage.setItem('sidebar', 'collapsed')
localStorage.removeItem('sidebar')쿠키도 document.cookie로 접근할 수 있다.
하지만 HttpOnly 속성이 붙은 쿠키는 JavaScript에서 읽을 수 없다.
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax이 속성은 로그인 세션 쿠키에서 중요하다.
XSS 취약점이 생겼을 때 JavaScript가 세션 쿠키를 직접 읽어 훔쳐가는 위험을 줄일 수 있기 때문이다.
반대로 localStorage에 저장된 값은 같은 origin에서 실행되는 JavaScript가 읽을 수 있다.
그래서 XSS가 발생하면 localStorage에 있는 access token 같은 값이 노출될 수 있다.
const token = localStorage.getItem('accessToken')민감한 인증 정보를 저장할 때 localStorage가 항상 편한 선택은 아니다.
로그인 세션에는 HttpOnly, Secure, SameSite를 갖춘 쿠키가 더 적합한 경우가 많다.
만료와 삭제
쿠키는 서버가 만료 시간을 정할 수 있다.Max-Age는 초 단위의 수명을 의미하고, Expires는 특정 날짜를 의미한다.
Set-Cookie: theme=dark; Max-Age=2592000; Path=/쿠키를 지울 때도 같은 이름과 범위로 다시 설정하면서 만료 시간을 과거로 두거나 Max-Age=0을 사용할 수 있다.
Set-Cookie: theme=; Max-Age=0; Path=/localStorage에는 자체 만료 시간이 없다.
값은 사용자가 브라우저 데이터를 삭제하거나 코드에서 지우기 전까지 유지될 수 있다.
localStorage.removeItem('theme')
localStorage.clear()만료가 필요한 값을 localStorage에 저장한다면 만료 시각을 함께 저장하고 직접 확인해야 한다.
localStorage.setItem(
'notice',
JSON.stringify({
value: 'hidden',
expiresAt: Date.now() + 1000 * 60 * 60 * 24,
}),
)이런 코드가 늘어난다면 서버 쿠키나 다른 저장 방식을 다시 보는 편이 좋다.
용량과 성능
쿠키는 매 요청에 실릴 수 있으므로 작게 유지해야 한다.
쿠키가 커지면 같은 사이트로 보내는 HTTP 요청마다 불필요한 데이터가 같이 이동할 수 있다.
그래서 쿠키에는 큰 JSON이나 긴 목록을 넣지 않는 편이 좋다.
보통 서버가 조회할 수 있는 짧은 식별자나 작은 설정값 정도가 적절하다.
localStorage는 요청에 자동으로 실리지 않으므로 클라이언트 전용 설정을 저장하기 편하다.
하지만 큰 데이터를 많이 넣어도 무한히 안전한 것은 아니다.
브라우저 저장소에는 quota가 있고, Web Storage API는 동기적으로 동작한다.
MDN 문서도 localStorage와 sessionStorage 작업이 동기적으로 실행되어 JavaScript 실행을 막을 수 있다고 설명한다.
많은 데이터를 저장하거나 자주 읽고 써야 한다면 IndexedDB 같은 비동기 저장소를 고려하는 편이 낫다.
보안 기준
쿠키를 쓸 때는 속성을 함께 봐야 한다.Secure는 HTTPS 요청에서만 쿠키를 보내도록 한다.HttpOnly는 JavaScript 접근을 막는다.SameSite는 cross-site 요청에서 쿠키가 보내지는 방식을 제한한다.
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax이런 속성을 붙였다고 해서 모든 보안 문제가 사라지는 것은 아니다.
하지만 로그인 세션 쿠키를 다룰 때는 기본적으로 검토해야 하는 값이다.
localStorage는 JavaScript 접근이 쉬운 만큼 XSS에 취약한 값을 넣지 않는 편이 좋다.
특히 access token, refresh token처럼 탈취되면 계정 권한으로 이어질 수 있는 값은 신중해야 한다.
저장소 선택은 보안의 일부일 뿐이다.
입력값 이스케이프, Content Security Policy, CSRF 방어, HTTPS, 서버 측 세션 만료도 함께 봐야 한다.
선택 기준
서버가 매 요청에서 자동으로 받아야 하는 값이면 쿠키를 먼저 본다.
로그인 세션 식별자, 서버가 관리하는 장바구니 식별자, A/B 테스트 식별자 같은 값이 여기에 가깝다.
클라이언트 화면에서만 필요한 값이면 localStorage가 단순하다.
다크 모드, 사이드바 접힘 상태, 마지막으로 선택한 탭, 임시 필터 설정 같은 값이 여기에 가깝다.
민감한 인증 정보를 저장해야 한다면 localStorage에 바로 넣기 전에 다시 생각한다.
JavaScript에서 읽을 수 있다는 특성은 편하지만, 공격자가 스크립트를 실행할 수 있는 상황에서는 위험해질 수 있다.
정리하면 기준은 단순하다.
자동으로 서버에 보내야 하면 쿠키다.
브라우저 화면에서만 쓰면 localStorage다.
로그인 세션처럼 민감한 값은 HttpOnly, Secure, SameSite를 갖춘 쿠키를 우선 검토한다.
참고 자료








