쿠키란 무엇인가 — HTTP 상태 관리
이 토픽을 마치면
HTTP가 왜 "상태 없는" 프로토콜인지 이해하고, 쿠키가 이 문제를 어떻게 해결하는지 설명할 수 있으며, Express에서 쿠키를 설정하고 읽는 방법을 알게 됩니다.
HTTP는 기억력이 없다
HTTP 프로토콜은 무상태(stateless) 입니다. 서버는 요청을 하나 처리하고 나면 클라이언트를 완전히 잊어버립니다.
Client: GET /page1 → Server: "Page 1 내용이요"
Client: GET /page2 → Server: "누구세요? 처음 뵙겠습니다. Page 2 내용이요"브라우저가 1초 전에 /page1을 요청했던 같은 사용자인지, 서버는 알 수 없습니다. 로그인도, 장바구니도, 다크모드 설정도 — "이전 요청을 기억"해야 하는 모든 기능이 이 무상태 특성 때문에 불가능합니다.
쿠키는 이 문제를 해결합니다. 서버가 클라이언트에게 "이 정보를 저장해두고, 다음에 올 때 보여줘"라고 하는 것입니다.
쿠키의 작동 원리
쿠키는 HTTP 헤더를 통해 주고받습니다.
1. 서버 → 클라이언트 (응답 헤더)
Set-Cookie: username=Hoon; Path=/
2. 클라이언트 → 서버 (다음 요청의 요청 헤더)
Cookie: username=Hoon한 번 설정하면, 브라우저는 같은 도메인의 모든 요청에 쿠키를 자동으로 포함합니다. 서버가 시키지 않아도 브라우저가 알아서 보내줍니다. 이것이 쿠키의 핵심 메커니즘입니다.
Express에서 쿠키 다루기
쿠키 설정
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
// Set-Cookie 헤더를 보냄
res.cookie('username', 'Hoon', {
maxAge: 24 * 60 * 60 * 1000, // 1 day (milliseconds)
httpOnly: true,
secure: false, // HTTPS에서만 전송 (개발 중 false)
sameSite: 'lax'
});
res.json({ message: 'Logged in, cookie set' });
});쿠키 읽기
쿠키를 읽으려면 cookie-parser 미들웨어가 필요합니다.
npm install cookie-parserconst cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/profile', (req, res) => {
const username = req.cookies.username;
if (!username) {
return res.status(401).json({ error: 'Not logged in' });
}
res.json({ message: `Welcome back, ${username}` });
});cookie-parser가 Cookie 헤더를 파싱해서 req.cookies 객체로 만들어줍니다. 이 미들웨어 없이는 헤더를 직접 파싱해야 합니다.
쿠키 삭제
app.get('/logout', (req, res) => {
res.clearCookie('username');
res.json({ message: 'Logged out, cookie cleared' });
});clearCookie는 같은 이름의 쿠키를 즉시 만료시킵니다.
쿠키의 속성들
| 속성 | 설명 | 예시 |
|---|---|---|
maxAge | 수명 (밀리초). 이 시간이 지나면 자동 삭제 | 86400000 (1일) |
expires | 만료 일시 (Date 객체). maxAge보다 덜 쓰임 | new Date('2026-12-31') |
httpOnly | JavaScript에서 접근 불가 (XSS 방어 핵심) | true |
secure | HTTPS 연결에서만 전송 | true |
sameSite | 다른 사이트에서의 요청에 쿠키 포함 여부 | 'strict', 'lax', 'none' |
path | 이 경로 하위에서만 쿠키 전송 | '/' |
domain | 쿠키가 유효한 도메인 | '.example.com' |
이 속성들 중 httpOnly와 secure가 보안에서 가장 중요합니다.
보안 — 쿠키를 안전하게
httpOnly
// Bad — JavaScript로 쿠키 접근 가능 (XSS 취약)
res.cookie('token', 'abc123');
// Good — JavaScript로 접근 불가
res.cookie('token', 'abc123', { httpOnly: true });httpOnly: true를 설정하면 document.cookie로 읽을 수 없습니다. XSS 공격자가 스크립트를 주입해도 쿠키를 훔칠 수 없습니다. 인증 관련 쿠키에는 반드시 설정해야 합니다.
secure
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true // HTTPS에서만 전송
});HTTP(암호화 안 됨) 연결에서는 쿠키가 전송되지 않습니다. 중간자(MITM)가 쿠키를 가로챌 수 없습니다.
sameSite
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict' // 같은 사이트 요청에서만 전송
});| 값 | 동작 |
|---|---|
'strict' | 같은 사이트에서 온 요청만 쿠키 포함. CSRF 완전 차단. 단, 외부 링크로 진입 시 로그아웃 상태 |
'lax' | GET 요청(링크 클릭)은 허용, POST는 차단. 대부분의 CSRF 방어 + 편의성 절충. 권장 |
'none' | 모든 요청에 포함. secure: true 필수. 크로스사이트 API에서만 사용 |
쿠키 vs 로컬 스토리지
| 쿠키 | localStorage | |
|---|---|---|
| 서버 전송 | 자동으로 매 요청에 포함 | 전송되지 않음 |
| 용량 | ~4KB | ~5MB |
| 만료 | 자동 만료 가능 | 수동 삭제만 |
| 접근 | httpOnly이면 JS 접근 불가 | JS로만 접근 |
| 용도 | 인증, 세션, 서버 설정 | UI 상태, 캐시, 오프라인 데이터 |
"서버가 알아야 하는 정보"는 쿠키, "브라우저만 알면 되는 정보"는 localStorage입니다.
실전 패턴 — 다크모드 쿠키
// 다크모드 설정 — 서버에서 렌더링 시 적용 가능
app.post('/settings/theme', (req, res) => {
const { theme } = req.body; // 'dark' or 'light'
res.cookie('theme', theme, {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year
httpOnly: false, // CSS 적용 위해 JS 접근 필요
sameSite: 'lax'
});
res.json({ theme });
});
app.get('/', (req, res) => {
const theme = req.cookies.theme || 'light';
res.render('index', { theme });
});다크모드처럼 보안에 민감하지 않은 설정은 httpOnly: false로 해도 됩니다. 하지만 인증 토큰은 절대 httpOnly: false로 설정하면 안 됩니다.
핵심 정리
| 개념 | 한 줄 정리 |
|---|---|
| HTTP 무상태 | 서버는 이전 요청을 기억하지 않음 |
| 쿠키 | 서버가 클라이언트에게 맡기는 작은 데이터 조각 |
| Set-Cookie | 서버 → 클라이언트 (응답 헤더) |
| Cookie | 클라이언트 → 서버 (요청 헤더, 자동) |
| httpOnly | JS 접근 차단 (XSS 방어) |
| secure | HTTPS에서만 전송 |
| sameSite | CSRF 방어 (lax 권장) |
쿠키는 웹의 가장 오래된 상태 관리 메커니즘이지만, 여전히 가장 널리 쓰입니다. 세션, 인증, 사용자 설정 — 이 모든 것의 기반이 쿠키입니다. 보안 속성(httpOnly, secure, sameSite)을 빠뜨리면 XSS와 CSRF에 노출되므로, 쿠키를 설정할 때는 항상 이 세 가지를 먼저 체크합니다.
실전 체크리스트
인증 쿠키를 설정할 때 반드시 확인해야 할 항목:
res.cookie('session_token', token, {
httpOnly: true, // ✓ XSS 방어 — JS에서 접근 불가
secure: true, // ✓ HTTPS 전용 — 평문 전송 차단
sameSite: 'lax', // ✓ CSRF 방어 — 크로스사이트 POST 차단
maxAge: 3600000, // ✓ 만료 설정 — 영구 쿠키 방지
path: '/' // ✓ 경로 제한 — 필요한 범위만
});이 다섯 가지를 하나라도 빠뜨리면 보안 취약점이 됩니다. 프로덕션에 배포하기 전에 이 체크리스트를 반드시 확인합니다.