Claude-skill-registry csrf-protection
Implement Cross-Site Request Forgery (CSRF) protection using tokens, SameSite cookies, and origin validation. Use when building forms and state-changing operations.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/csrf-protection" ~/.claude/skills/majiayu000-claude-skill-registry-csrf-protection && rm -rf "$T"
manifest:
skills/data/csrf-protection/SKILL.mdsource content
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
// csrf-protection.js const crypto = require('crypto'); const csrf = require('csurf'); class CSRFProtection { constructor() { this.tokens = new Map(); this.tokenExpiry = 3600000; // 1 hour } /** * Generate CSRF token */ generateToken() { return crypto.randomBytes(32).toString('hex'); } /** * Create token for session */ createToken(sessionId) { const token = this.generateToken(); const expiry = Date.now() + this.tokenExpiry; this.tokens.set(sessionId, { token, expiry }); return token; } /** * Validate CSRF 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) ); } /** * Express middleware */ middleware() { return (req, res, next) => { // Skip GET, HEAD, OPTIONS 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(); }; } } // Express setup with csurf package const express = require('express'); const session = require('express-session'); const cookieParser = require('cookie-parser'); const app = express(); // Session configuration app.use(cookieParser()); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3600000 } })); // CSRF protection middleware const csrfProtection = csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }); app.use(csrfProtection); // Provide token to templates app.use((req, res, next) => { res.locals.csrfToken = req.csrfToken(); next(); }); // API endpoint to get CSRF token app.get('/api/csrf-token', (req, res) => { res.json({ csrfToken: req.csrfToken() }); }); // Protected route app.post('/api/transfer', csrfProtection, (req, res) => { const { amount, toAccount } = req.body; // Process transfer res.json({ message: 'Transfer successful', amount, toAccount }); }); // Error handler for CSRF errors 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
// double-submit-csrf.js const crypto = require('crypto'); class DoubleSubmitCSRF { /** * Generate CSRF token and set cookie */ static generateAndSetToken(res) { const token = crypto.randomBytes(32).toString('hex'); // Set CSRF cookie res.cookie('XSRF-TOKEN', token, { httpOnly: false, // Allow JS to read for double submit secure: true, sameSite: 'strict', maxAge: 3600000 }); return token; } /** * Middleware to validate double submit */ static middleware() { return (req, res, next) => { // Skip GET, HEAD, OPTIONS if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) { return next(); } const cookieToken = req.cookies['XSRF-TOKEN']; const headerToken = req.headers['x-xsrf-token']; if (!cookieToken || !headerToken) { return res.status(403).json({ error: 'csrf_token_missing' }); } // Compare tokens (timing-safe) if (!crypto.timingSafeEqual( Buffer.from(cookieToken), Buffer.from(headerToken) )) { return res.status(403).json({ error: 'csrf_token_mismatch' }); } next(); }; } } // Express setup const app = express(); const cookieParser = require('cookie-parser'); app.use(cookieParser()); app.use(express.json()); // Generate token on login app.post('/api/login', async (req, res) => { // Authenticate user const token = DoubleSubmitCSRF.generateAndSetToken(res); res.json({ message: 'Login successful', csrfToken: token }); }); // Protected routes app.use('/api/*', DoubleSubmitCSRF.middleware()); app.post('/api/update-profile', (req, res) => { // Update profile res.json({ message: 'Profile updated' }); });
3. Python Flask CSRF Protection
# csrf_protection.py 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 # 1 hour app.config['WTF_CSRF_SSL_STRICT'] = True csrf = CSRFProtect(app) # Cookie configuration 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('/api/csrf-token', methods=['GET']) def get_csrf_token(): """Provide CSRF token to clients""" token = generate_csrf() return jsonify({'csrfToken': token}) @app.route('/api/transfer', methods=['POST']) def transfer_funds(): """Protected endpoint""" data = request.get_json() return jsonify({ 'message': 'Transfer successful', 'amount': data.get('amount') }) # Custom CSRF decorator def require_csrf(f): @wraps(f) def decorated_function(*args, **kwargs): if request.method in ['POST', 'PUT', 'DELETE']: token = request.headers.get('X-CSRF-Token') if not token: return jsonify({'error': 'CSRF token required'}), 403 try: validate_csrf(token) except: return jsonify({'error': 'Invalid CSRF token'}), 403 return f(*args, **kwargs) return decorated_function @app.route('/api/sensitive-action', methods=['POST']) @require_csrf def sensitive_action(): return jsonify({'message': 'Action completed'}) if __name__ == '__main__': app.run(ssl_context='adhoc')
4. Frontend CSRF Implementation
// csrf-client.js class CSRFClient { constructor() { this.token = null; this.tokenExpiry = null; } /** * Fetch CSRF token from server */ 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; // 1 hour return this.token; } /** * Get valid token (fetch if needed) */ async getToken() { if (!this.token || Date.now() > this.tokenExpiry) { await this.fetchToken(); } return this.token; } /** * Make protected request */ async request(url, options = {}) { const token = await this.getToken(); const headers = { 'Content-Type': 'application/json', 'X-CSRF-Token': token, ...options.headers }; return fetch(url, { ...options, headers, credentials: 'include' }); } /** * POST request with CSRF token */ async post(url, data) { return this.request(url, { method: 'POST', body: JSON.stringify(data) }); } /** * PUT request with CSRF token */ async put(url, data) { return this.request(url, { method: 'PUT', body: JSON.stringify(data) }); } /** * DELETE request with CSRF token */ async delete(url) { return this.request(url, { method: 'DELETE' }); } } // Usage const client = new CSRFClient(); async function transferFunds() { try { const response = await client.post('/api/transfer', { amount: 1000, toAccount: '123456' }); const result = await response.json(); console.log('Transfer successful:', result); } catch (error) { console.error('Transfer failed:', error); } } // React hook for CSRF function useCSRF() { const [token, setToken] = React.useState(null); React.useEffect(() => { async function fetchToken() { const response = await fetch('/api/csrf-token'); const data = await response.json(); setToken(data.csrfToken); } fetchToken(); }, []); return token; } // Usage in React form function TransferForm() { const csrfToken = useCSRF(); const handleSubmit = async (e) => { e.preventDefault(); await fetch('/api/transfer', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, body: JSON.stringify({ amount: 1000, toAccount: '123456' }) }); }; return ( <form onSubmit={handleSubmit}> <input type="hidden" name="_csrf" value={csrfToken} /> {/* form fields */} <button type="submit">Transfer</button> </form> ); }
5. Origin and Referer Validation
// origin-validation.js function validateOrigin(req, res, next) { const allowedOrigins = [ 'https://example.com', 'https://app.example.com' ]; const origin = req.headers.origin; const referer = req.headers.referer; // Check Origin header if (origin && !allowedOrigins.includes(origin)) { return res.status(403).json({ error: 'invalid_origin' }); } // Check Referer header as fallback if (!origin && referer) { const refererUrl = new URL(referer); if (!allowedOrigins.includes(refererUrl.origin)) { return res.status(403).json({ error: 'invalid_referer' }); } } next(); } // Apply to state-changing routes 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
- CSRF tokens implemented
- SameSite cookies configured
- Origin/Referer validation
- Custom request headers
- Token expiration
- Secure cookie flags
- HTTPS enforced