| name | csrf-protection |
| description | Implement Cross-Site Request Forgery (CSRF) protection using tokens, SameSite cookies, and origin validation. Use when building forms and state-changing operations. |
CSRF Protection
Overview
Implement comprehensive Cross-Site Request Forgery protection using synchronizer tokens, double-submit cookies, SameSite cookie attributes, and custom headers.
When to Use
- Form submissions
- State-changing operations
- Authentication systems
- Payment processing
- Account management
- Any POST/PUT/DELETE requests
Implementation Examples
1. Node.js/Express CSRF Protection
const crypto = require('crypto');
const csrf = require('csurf');
class CSRFProtection {
constructor() {
this.tokens = new Map();
this.tokenExpiry = 3600000;
}
generateToken() {
return crypto.randomBytes(32).toString('hex');
}
createToken(sessionId) {
const token = this.generateToken();
const expiry = Date.now() + this.tokenExpiry;
this.tokens.set(sessionId, {
token,
expiry
});
return token;
}
validateToken(sessionId, token) {
const stored = this.tokens.get(sessionId);
if (!stored) {
return false;
}
if (Date.now() > stored.expiry) {
this.tokens.delete(sessionId);
return false;
}
return crypto.timingSafeEqual(
Buffer.from(stored.token),
Buffer.from(token)
);
}
middleware() {
return (req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const token = req.headers['x-csrf-token'] || req.body._csrf;
const sessionId = req.session?.id;
if (!token) {
return res.status(403).json({
error: 'csrf_token_missing',
message: 'CSRF token is required'
});
}
if (!this.validateToken(sessionId, token)) {
return res.status(403).json({
error: 'csrf_token_invalid',
message: 'Invalid or expired CSRF token'
});
}
next();
};
}
}
const express = require('express');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000
}
}));
const csrfProtection = csrf({
cookie: {
httpOnly: true,
secure: true,
sameSite: 'strict'
}
});
app.use(csrfProtection);
app.use((req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
app.get('/api/csrf-token', (req, res) => {
res.json({
csrfToken: req.csrfToken()
});
});
app.post('/api/transfer', csrfProtection, (req, res) => {
const { amount, toAccount } = req.body;
res.json({
message: 'Transfer successful',
amount,
toAccount
});
});
app.use((err, req, res, next) => {
if (err.code === 'EBADCSRFTOKEN') {
return res.status(403).json({
error: 'csrf_error',
message: 'Invalid CSRF token'
});
}
next(err);
});
module.exports = { CSRFProtection, csrfProtection };
2. Double Submit Cookie Pattern
const crypto = require('crypto');
class DoubleSubmitCSRF {
static generateAndSetToken(res) {
const token = crypto.randomBytes(32).toString('hex');
res.cookie('XSRF-TOKEN', token, {
httpOnly: false,
secure: true,
sameSite: 'strict',
maxAge: 3600000
});
return token;
}
static middleware() {
return (req, res, next) => {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const cookieToken = req.cookies['XSRF-TOKEN'];
const headerToken = req.headers[];
(!cookieToken || !headerToken) {
res.().({
:
});
}
(!crypto.(
.(cookieToken),
.(headerToken)
)) {
res.().({
:
});
}
();
};
}
}
app = ();
cookieParser = ();
app.(());
app.(express.());
app.(, (req, res) => {
token = .(res);
res.({
: ,
: token
});
});
app.(, .());
app.(, {
res.({ : });
});
3. Python Flask CSRF Protection
from flask import Flask, session, request, jsonify
from flask_wtf.csrf import CSRFProtect, generate_csrf, validate_csrf
from functools import wraps
import secrets
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['WTF_CSRF_TIME_LIMIT'] = 3600
app.config['WTF_CSRF_SSL_STRICT'] = True
csrf = CSRFProtect(app)
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Strict'
)
@app.before_request
def csrf_protect():
"""Validate CSRF token for state-changing methods"""
if request.method in ['POST', 'PUT', 'DELETE', 'PATCH']:
token = request.headers.get('X-CSRF-Token') or request.form.get('csrf_token')
if not token:
return jsonify({'error': 'CSRF token missing'}), 403
try:
validate_csrf(token)
except:
return jsonify({'error': 'Invalid CSRF token'}), 403
@app.route(, methods=[])
():
token = generate_csrf()
jsonify({: token})
():
data = request.get_json()
jsonify({
: ,
: data.get()
})
():
():
request.method [, , ]:
token = request.headers.get()
token:
jsonify({: }),
:
validate_csrf(token)
:
jsonify({: }),
f(*args, **kwargs)
decorated_function
():
jsonify({: })
__name__ == :
app.run(ssl_context=)
4. Frontend CSRF Implementation
class CSRFClient {
constructor() {
this.token = null;
this.tokenExpiry = null;
}
async fetchToken() {
const response = await fetch('/api/csrf-token', {
credentials: 'include'
});
const data = await response.json();
this.token = data.csrfToken;
this.tokenExpiry = Date.now() + 3600000;
return this.token;
}
async getToken() {
if (!this.token || Date.now() > this.tokenExpiry) {
await this.fetchToken();
}
return .;
}
() {
token = .();
headers = {
: ,
: token,
...options.
};
(url, {
...options,
headers,
:
});
}
() {
.(url, {
: ,
: .(data)
});
}
() {
.(url, {
: ,
: .(data)
});
}
() {
.(url, {
:
});
}
}
client = ();
() {
{
response = client.(, {
: ,
:
});
result = response.();
.(, result);
} (error) {
.(, error);
}
}
() {
[token, setToken] = .();
.( {
() {
response = ();
data = response.();
(data.);
}
();
}, []);
token;
}
() {
csrfToken = ();
= () => {
e.();
(, {
: ,
: {
: ,
: csrfToken
},
: .({
: ,
:
})
});
};
(
);
}
5. Origin and Referer Validation
function validateOrigin(req, res, next) {
const allowedOrigins = [
'https://example.com',
'https://app.example.com'
];
const origin = req.headers.origin;
const referer = req.headers.referer;
if (origin && !allowedOrigins.includes(origin)) {
return res.status(403).json({
error: 'invalid_origin'
});
}
if (!origin && referer) {
const refererUrl = new URL(referer);
if (!allowedOrigins.includes(refererUrl.origin)) {
return res.status(403).json({
error: 'invalid_referer'
});
}
}
next();
}
app.use('/api/*', validateOrigin);
Best Practices
✅ DO
- Use CSRF tokens for all state-changing operations
- Set SameSite=Strict on cookies
- Validate Origin/Referer headers
- Use secure, random tokens
- Implement token expiration
- Use HTTPS only
- Include tokens in AJAX requests
- Test CSRF protection
❌ DON'T
- Skip CSRF for authenticated requests
- Use GET for state changes
- Trust Origin header alone
- Reuse tokens
- Store tokens in localStorage
- Allow credentials in CORS without validation
CSRF Protection Methods
- Synchronizer Token: Server-generated tokens
- Double Submit Cookie: Cookie and header match
- SameSite Cookies: Browser-level protection
- Custom Headers: X-Requested-With
- Origin Validation: Check request origin
Defense Layers
Resources