쿠키 보안 옵션 (httpOnly, Secure, SameSite)
빠른 비교표
옵션 |
목적 |
기본값 |
설정 |
효과 |
httpOnly |
XSS 공격 방지 |
false |
httpOnly: true |
JavaScript 접근 불가 |
Secure |
중간자 공격 방지 |
false |
secure: true |
HTTPS만 전송 |
SameSite |
CSRF 공격 방지 |
Lax |
sameSite: 'strict' |
다른 사이트 요청 차단 |
상세 설명표
1. httpOnly
항목 |
설명 |
용도 |
XSS (Cross-Site Scripting) 공격 방지 |
설정 |
res.cookie('token', val, { httpOnly: true }) |
기본값 |
false (설정하지 않으면 JavaScript 접근 가능) |
효과 |
JavaScript에서 document.cookie 로 접근 불가 |
읽기 |
❌ JavaScript 불가 / ✅ HTTP 요청에 자동 포함 |
쓰기 |
❌ JavaScript에서 설정 불가 |
공격 방어 |
악성 스크립트가 실행되어도 쿠키 탈취 불가 |
권장 |
⭐⭐⭐⭐⭐ (Refresh Token 저장 시 필수) |
예시 |
httpOnly: true |
httpOnly 예시
// 서버
res.cookie('refreshToken', token, { httpOnly: true });
// 클라이언트 - 접근 시도
document.cookie; // ❌ 쿠키 보이지 않음
// ""
// 하지만 HTTP 요청에는 자동 포함
GET /api/user HTTP/1.1
Cookie: refreshToken=xyz789 // ✅ 자동 포함
// 서버에서는 받을 수 있음
req.cookies.refreshToken // ✅ "xyz789"
2. Secure
항목 |
설명 |
용도 |
중간자 공격(Man-in-the-Middle) 방지 |
설정 |
res.cookie('token', val, { secure: true }) |
기본값 |
false (HTTP도 전송) |
효과 |
HTTPS 연결에서만 쿠키 전송 |
HTTP |
❌ HTTP 요청에 쿠키 포함 안 됨 |
HTTPS |
✅ HTTPS 요청에 쿠키 포함 |
공격 방어 |
암호화되지 않은 연결로 쿠키 탈취 방지 |
권장 |
⭐⭐⭐⭐⭐ (프로덕션 필수) |
예시 |
secure: process.env.NODE_ENV === 'production' |
Secure 예시
// 서버
res.cookie('token', token, { secure: true });
// 개발 환경 (localhost:3000 - HTTP)
GET http://localhost:3000/api/user
Cookie: (없음) // ❌ 전송 안 됨
// 프로덕션 (https://api.example.com - HTTPS)
GET https://api.example.com/api/user
Cookie: token=xyz789 // ✅ 전송됨
3. SameSite
항목 |
설명 |
용도 |
CSRF (Cross-Site Request Forgery) 공격 방지 |
설정 |
res.cookie('token', val, { sameSite: 'strict' }) |
기본값 |
‘Lax’ (일부 크로스 사이트 요청에 포함) |
값 – Strict |
❌ 모든 크로스 사이트 요청에서 쿠키 미포함 (가장 안전) |
값 – Lax |
⚠️ 안전한 메서드(GET)의 크로스 사이트만 포함 (기본값) |
값 – None |
✅ 모든 크로스 사이트 요청에 포함 (Secure 필수) |
공격 방어 |
악의적인 웹사이트에서의 자동 요청 차단 |
권장 |
⭐⭐⭐⭐⭐ (‘strict’ 또는 ‘lax’ 권장) |
예시 |
sameSite: 'strict' |
SameSite 값 비교
SameSite |
같은 사이트 |
크로스 사이트 (GET) |
크로스 사이트 (POST) |
보안 |
Strict |
✅ |
❌ |
❌ |
⭐⭐⭐ (매우 강함) |
Lax (기본) |
✅ |
✅ |
❌ |
⭐⭐ (중간) |
None |
✅ |
✅ |
✅ |
⭐ (약함, Secure 필수) |
SameSite 예시
// 서버
res.cookie('token', token, { sameSite: 'strict' });
// 정상 요청 (같은 사이트)
GET https://example.com/api/user
Cookie: token=xyz789 // ✅ 포함됨
// 악의적인 요청 (다른 사이트에서)
// hacker.com에서 example.com으로 자동 요청
GET https://example.com/api/user
Cookie: token=xyz789 // ❌ Strict이면 미포함
CSRF 공격 예시 (SameSite로 방어)
SameSite 없을 때 (위험)
<!-- hacker.com (악의적인 사이트) -->
<img src="https://bank.com/api/transfer?amount=1000&to=hacker" />
<!-- bank.com의 쿠키가 자동으로 포함됨 -->
GET https://bank.com/api/transfer?amount=1000&to=hacker
Cookie: sessionToken=xyz789 // ❌ 자동 포함 (위험!)
<!-- 공격 성공: 사용자 모르게 돈 이체됨 -->
SameSite: strict 설정 시 (안전)
<!-- hacker.com (악의적인 사이트) -->
<img src="https://bank.com/api/transfer?amount=1000&to=hacker" />
<!-- bank.com의 쿠키가 포함 안 됨 -->
GET https://bank.com/api/transfer?amount=1000&to=hacker
Cookie: (없음) // ✅ 쿠키 미포함 (안전!)
<!-- 공격 실패: 인증 없이 요청 거부됨 -->
실제 구현 코드
개발 환경 설정
// server.js
app.post('/api/login', (req, res) => {
const token = jwt.sign({ userId: 1 }, SECRET);
// 개발/프로덕션 구분
const isProduction = process.env.NODE_ENV === 'production';
res.cookie('refreshToken', token, {
httpOnly: true, // ✅ XSS 방지
secure: isProduction, // ✅ HTTPS에서만 전송 (프로덕션)
sameSite: 'strict', // ✅ CSRF 방지
maxAge: 7 * 24 * 60 * 60 * 1000 // 7일
});
res.json({ accessToken: token });
});
프로덕션 환경 권장 설정
res.cookie('refreshToken', token, {
httpOnly: true, // 필수
secure: true, // HTTPS만
sameSite: 'strict', // 크로스 사이트 차단
domain: 'example.com', // 특정 도메인만
path: '/', // 루트 경로
maxAge: 7 * 24 * 60 * 60 * 1000
});
공격 유형별 방어 정리
공격 유형 |
설명 |
방어 방법 |
옵션 |
XSS |
JavaScript로 쿠키 탈취 |
JavaScript 접근 차단 |
httpOnly: true |
중간자 공격 |
암호화 없는 연결에서 탈취 |
암호화 통신만 허용 |
secure: true |
CSRF |
다른 사이트에서 자동 요청 |
크로스 사이트 요청 차단 |
sameSite: 'strict' |
요약 표
각 옵션의 필요성
httpOnly
├─ 보호: XSS 공격
├─ 원리: JavaScript 접근 불가
├─ 자동 전송: ✅ (HTTP 요청에 자동 포함)
└─ 필수도: ⭐⭐⭐⭐⭐
Secure
├─ 보호: 중간자 공격
├─ 원리: HTTPS만 전송
├─ 자동 전송: ✅ (HTTPS 연결에서만)
└─ 필수도: ⭐⭐⭐⭐⭐
SameSite
├─ 보호: CSRF 공격
├─ 원리: 크로스 사이트 요청 차단
├─ 자동 전송: ⚠️ (설정값에 따라 다름)
└─ 필수도: ⭐⭐⭐⭐
결론: 권장 설정
// ✅ 프로덕션 환경 최상의 보안
res.cookie('refreshToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
// ✅ 개발 환경 (localhost)
res.cookie('refreshToken', token, {
httpOnly: true,
secure: false, // localhost에서는 필요 없음
sameSite: 'lax', // 개발 편의성
maxAge: 7 * 24 * 60 * 60 * 1000
});