Cookie 가 자동 전송되는 점을 노린 공격. SameSite=Strict/Lax 가 1차 방어, CSRF token (double submit) 이 2차. Authorization header (JWT in memory) 사용 시엔 CSRF 자체가 거의 무관.
📖 핵심 개념
공격자: evil.com 의 form 이 자동으로 bank.com/transfer 로 POST. 사용자 cookie 가 자동 첨부 → 인증 통과.
방어:
SameSite cookie: 다른 origin 에서 cookie 안 보냄.
CSRF token: 매 form 마다 고유 token, 서버가 cookie 와 form 두 곳 비교.
Origin / Referer header 검증: 보조.
JWT in Authorization header (cookie 아님): CSRF 자동 면역.
💻 코드 패턴
SameSite cookie (1차 방어)
res.cookie('session',token,{httpOnly: true,secure: true,sameSite:'strict',// 또는 'lax' (top-level GET 허용)
path:'/',maxAge: 7*24*60*60*1000,});
Strict: cross-site 어떤 요청도 cookie 안 감. 외부 링크 클릭으로 들어와도 cookie X (UX 영향).
Lax: top-level GET 만 OK. 폼 POST / iframe 차단.
None: cross-site OK — Secure 필수. 거의 안 씀.
Double submit cookie (CSRF token)
importcsrffrom'csurf';app.use(csrf({cookie: true}));// 페이지 렌더 시
app.get('/dashboard',(req,res)=>{res.render('dashboard',{csrfToken: req.csrfToken()});});// → form 안에 <input type="hidden" name="_csrf" value="...">
// 모든 POST 가 자동 검증
app.post('/transfer',(req,res)=>{// csurf 가 이미 검증
doTransfer(req.body);});
Custom header — SPA
// CSRF token을 cookie + JS-readable form 둘 다.
// SPA 가 fetch 시 X-CSRF-Token 헤더로 전송.
res.cookie('csrf_token',token,{httpOnly: false,sameSite:'strict',secure: true});// 서버 검증
app.use((req,res,next)=>{if(['POST','PUT','DELETE','PATCH'].includes(req.method)){constcookie=req.cookies.csrf_token;constheader=req.header('x-csrf-token');if(!cookie||cookie!==header)returnres.status(403).end();}next();});// 클라이언트
fetch('/api/x',{method:'POST',headers:{'X-CSRF-Token':getCookie('csrf_token')},credentials:'include',});