결론부터 말하면 쿠키 vs 세션 vs 토큰은 같은 층위의 개념이 아니다. 쿠키는 운반 수단이고, 세션과 토큰은 인증 데이터를 어디서 관리하느냐의 차이다.
셋을 나란히 비교하는 글이 많지만 비교 자체가 약간 잘못된 프레임이다. 쿠키 없이 세션 ID를 전달하기 어렵고, 쿠키 안에 JWT를 담는 것도 흔한 패턴이다. 역할을 구분하면 선택 기준이 보인다.

쿠키: 데이터를 운반하는 그릇
쿠키는 인증 방식이 아니라 브라우저-서버 간 데이터 저장 메커니즘이다. 서버가 Set-Cookie 헤더로 값을 내려보내면 브라우저가 이후 요청마다 자동으로 포함시킨다.
# 서버 응답
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
# 이후 모든 요청에 자동 포함
Cookie: sessionId=abc123
쿠키는 그릇이다. 그릇 안에 세션 ID를 담으면 세션 기반 인증, JWT를 담으면 토큰 기반 인증이 된다.
세션 기반 인증: 서버가 상태를 기억한다
로그인 시 사용자 정보를 서버에 저장하고, 클라이언트에는 세션 ID만 쿠키로 내려보낸다. 이후 요청마다 서버가 세션 ID로 사용자를 조회한다.
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, secure: true, maxAge: 3600000 }
}));
// 로그인 처리
app.post('/login', (req, res) => {
// 인증 통과 후
req.session.userId = user.id;
res.send('로그인 성공');
});
장점: 서버가 세션을 즉시 무효화할 수 있다. 강제 로그아웃이 간단하다.
단점: 사용자 수만큼 서버 메모리가 늘어난다. 서버 여러 대를 쓰면 Redis 같은 세션 공유 솔루션이 필요하다.
토큰 기반 인증: 클라이언트가 증명서를 들고 다닌다
로그인 시 서버가 사용자 정보를 담은 토큰(주로 JWT)을 발급한다. 클라이언트가 이 토큰을 보관하고, 이후 요청마다 Authorization 헤더에 포함시킨다. 서버는 토큰 자체를 검증할 뿐 저장하지 않는다.
const jwt = require('jsonwebtoken');
// 로그인 시 토큰 발급
app.post('/login', (req, res) => {
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
});
// 요청 검증 미들웨어
function verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ message: '토큰 없음' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ message: '토큰 무효' });
}
}
JWT 구조가 궁금하면 jwt.io에서 토큰을 직접 디코딩해볼 수 있다.
장점: 상태 없음(Stateless)으로 서버 확장이 쉽다. 여러 서버 간 세션 공유가 불필요하고, 모바일 앱에 적합하다.
단점: 토큰 즉시 무효화가 어렵다. 탈취된 토큰은 만료 전까지 유효하다.
쿠키 세션 토큰 핵심 차이 비교
| 항목 | 세션 기반 | 토큰 기반 |
|---|---|---|
| 상태 저장 위치 | 서버 | 클라이언트 |
| 확장성 | 낮음 (Redis 필요) | 높음 (Stateless) |
| 즉시 로그아웃 | 가능 | 어려움 |
| 서버 부하 | 세션 저장 비용 | 검증 연산 비용 |
| 보안 위협 | 세션 하이재킹 | 토큰 탈취, XSS |
| 적합한 환경 | 전통적 웹 앱, 금융 | REST API, 모바일, 마이크로서비스 |
보안 설정: HttpOnly 쿠키와 CSRF 방어
세션 ID든 토큰이든 쿠키에 담는다면 세 가지 속성은 필수다. 빠뜨리면 XSS나 CSRF 공격에 노출된다.
res.cookie('token', jwtToken, {
httpOnly: true, // JavaScript 접근 불가 -- XSS 방어
secure: true, // HTTPS 전송만 허용
sameSite: 'strict', // 크로스사이트 요청 차단 -- CSRF 방어
maxAge: 3600000 // 1시간 만료
});
HttpOnly: JavaScript로 쿠키를 읽을 수 없게 한다. document.cookie로 접근이 막히므로 XSS 공격으로 토큰을 탈취하기 어려워진다.
SameSite=Strict: 다른 사이트에서 시작된 요청에 쿠키를 포함하지 않는다. CSRF 공격을 효과적으로 차단한다. Lax는 일부 서드파티 요청을 허용하는 완화 설정이다.
localStorage에 토큰을 저장하면 HttpOnly 보호를 받을 수 없다. XSS로 스크립트를 심으면 localStorage의 토큰을 그대로 읽을 수 있다. 보안이 중요한 서비스라면 HttpOnly 쿠키에 저장하는 게 낫다. MDN: Using HTTP cookies 문서에 쿠키 속성 전체 레퍼런스가 있다.
Access Token + Refresh Token 패턴
실무에서 가장 흔한 구성이다. Access Token은 만료를 짧게(15분~1시간), Refresh Token은 길게(7~30일) 설정한다. 단기 토큰이 탈취돼도 피해 범위를 줄일 수 있다.
1. 로그인 -> Access Token(1시간) + Refresh Token(7일) 발급
2. 요청마다 Authorization 헤더에 Access Token 포함
3. Access Token 만료 -> Refresh Token으로 재발급 요청
4. Refresh Token도 만료 -> 재로그인
// Access Token 만료 시 재발급 엔드포인트
app.post('/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken; // HttpOnly 쿠키에서 읽음
try {
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
const newAccessToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ accessToken: newAccessToken }); // 재발급
} catch {
res.status(401).json({ message: '재로그인 필요' });
}
});
Refresh Token은 HttpOnly 쿠키에, Access Token은 메모리(변수)에 보관하는 조합이 보안 상 유리하다. Access Token을 localStorage에 저장하면 XSS로 탈취 가능하다.
언제 뭘 쓸까
판단 기준은 서비스 규모와 아키텍처다.
세션을 쓸 때:
- 소규모 서비스, 단일 서버
- 즉시 강제 로그아웃이 필요한 서비스 (금융, 관리자 패널)
- 전통적인 서버 사이드 렌더링 앱
토큰을 쓸 때:
- 분산 환경, 서버 여러 대
- REST API 서버 (모바일 앱 백엔드)
- 마이크로서비스 아키텍처
- 웹 + 모바일 동시 지원
사이드 프로젝트 초기라면 세션이 설정이 단순하다. 확장을 고려한 API 서버라면 토큰 + Refresh Token 패턴이 낫다. JWT를 쿠키 저장 방식으로 쓰면 보안과 Stateless 장점을 동시에 챙길 수 있다.
자주 묻는 질문
JWT를 쿠키에 담으면 세션 기반 인증인가?
아니다. 운반 수단(쿠키)과 상태 관리 방식(세션 vs 토큰)은 별개다. 쿠키에 JWT를 담아도 서버가 상태를 저장하지 않으면 토큰 기반 인증이다. HttpOnly 쿠키에 JWT를 저장하는 패턴이 보안 상 권장된다.
localStorage에 토큰을 저장해도 되나?
권장하지 않는다. localStorage는 JavaScript로 접근 가능해서 XSS 공격에 노출된다. HttpOnly 쿠키에 저장하면 JavaScript 접근을 막아 XSS 방어가 된다.
세션은 구식 기술인가?
아니다. 즉시 로그아웃이 필요한 서비스(뱅킹, 관리자 패널)는 세션이 더 적합하다. 토큰은 만료 전까지 무효화가 어렵기 때문이다. 기술의 우열이 아니라 요구사항에 맞는 선택이 중요하다.
한줄 정리: 쿠키는 운반 수단, 세션은 서버 저장, 토큰은 클라이언트 저장이다. 즉시 로그아웃이 필요하면 세션, 확장성이 필요하면 토큰 + Refresh Token 패턴을 쓰자.