CSRF Protection
Token patterns, SameSite cookies, double submit, framework middleware.
securitybackendfrontend
# CSRF Protection
## What is CSRF?
Attacker tricks user's browser into making requests to your site using the user's session.
```html
<!-- On evil.com -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
<form action="https://bank.com/transfer" method="POST" id="f">
<input name="to" value="attacker" />
<input name="amount" value="10000" />
</form>
<script>document.getElementById('f').submit()</script>
```
## Defense 1: SameSite cookies (primary)
```ts
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'lax', // blocks cross-site POST
// sameSite: 'strict' // blocks ALL cross-site requests
maxAge: 86400000,
})
```
`Lax`: blocks cross-site POST/PUT/DELETE. Allows GET navigation (links).
`Strict`: blocks ALL cross-site requests including navigation.
## Defense 2: CSRF token
```ts
// Server: generate token per session
import crypto from 'node:crypto'
const csrfToken = crypto.randomBytes(32).toString('hex')
req.session.csrfToken = csrfToken
// Send token to client (in HTML or cookie)
<input type="hidden" name="_csrf" value="{csrfToken}" />
// Verify on mutation
if (req.body._csrf !== req.session.csrfToken) {
return res.status(403).json({ error: 'Invalid CSRF token' })
}
```
## Defense 3: Check Origin header
```ts
function verifyCsrf(req, res, next) {
if (['POST','PUT','DELETE','PATCH'].includes(req.method)) {
const origin = req.headers.origin || req.headers.referer
if (!origin?.startsWith('https://yourdomain.com')) {
return res.status(403).json({ error: 'CSRF rejected' })
}
}
next()
}
```
## Modern SPAs
- API-only backends using `Authorization: Bearer` header are **immune** to CSRF.
(Browsers don't auto-attach custom headers cross-origin.)
- If using cookies for auth in SPA: use `SameSite=Lax` + CSRF token.
## Checklist
- [ ] `SameSite=Lax` or `Strict` on all auth cookies.
- [ ] CSRF token for cookie-based sessions.
- [ ] Validate `Origin` header on mutations.
- [ ] Never use GET for state-changing operations.
API: /api/skills/csrf-protection