--- name: csrf-protection description: Implement Cross-Site Request Forgery (CSRF) protection for API routes. Use this skill when you need to protect POST/PUT/DELETE endpoints, implement token validation, prevent cross-site attacks, or secure form submissions. Triggers include "CSRF", "cross-site request forgery", "protect form", "token validation", "withCsrf", "CSRF token", "session fixation". --- # CSRF Protection - Preventing Cross-Site Request Forgery ## What CSRF Attacks Are ### The Attack Scenario Imagine you're logged into your banking app. In another tab, you visit a malicious website. That website contains hidden code that submits a form to your bank: "Transfer $10,000 to attacker's account." Because you're logged in, your browser automatically sends your session cookie, and the bank processes the transfer. This is **Cross-Site Request Forgery**—tricking your browser into making requests you didn't intend. ### Real-World CSRF Attacks **Router DNS Hijacking (2008):** A CSRF vulnerability in several home routers allowed attackers to change router DNS settings by tricking users into visiting a malicious website. Victims lost no money but were redirected to phishing sites for months. Millions of routers were affected. **YouTube Actions (2012):** YouTube had a CSRF vulnerability that allowed attackers to perform actions as other users (like, subscribe, etc.) by tricking them into visiting a crafted URL. ### Why CSRF Is Still Common According to OWASP, CSRF vulnerabilities appear in **35% of web applications tested**. Why? - It's invisible when it works (users don't know they made a request) - Easy to forget to implement (no obvious broken functionality) - Developers often rely solely on authentication without checking request origin ## Our CSRF Architecture ### Implementation Features 1. **HMAC-SHA256 Cryptographic Signing** (industry standard) - Provides cryptographic proof token was generated by our server - Even if intercepted, attackers can't forge tokens without secret key 2. **Session-Bound Tokens** - Tokens can't be used across different user sessions - Each user gets unique tokens 3. **Single-Use Tokens** - Token cleared after validation - Window of opportunity is seconds, not hours - If captured, useless after one request 4. **HTTP-Only Cookies** - JavaScript cannot access tokens - Prevents XSS-based token theft 5. **SameSite=Strict** - Browser won't send cookie on cross-origin requests - Additional layer of protection ### Implementation Files - `lib/csrf.ts` - Cryptographic token generation - `lib/withCsrf.ts` - Middleware enforcing verification - `app/api/csrf/route.ts` - Token endpoint for clients ## How to Use CSRF Protection ### Step 1: Wrap Your Handler For any POST/PUT/DELETE endpoint: ```typescript import { NextRequest, NextResponse } from 'next/server'; import { withCsrf } from '@/lib/withCsrf'; async function handler(request: NextRequest) { // Your business logic here // Token automatically verified by withCsrf return NextResponse.json({ success: true }); } // Apply CSRF protection export const POST = withCsrf(handler); export const config = { runtime: 'nodejs', // Required for crypto operations }; ``` ### Step 2: Client-Side Token Fetching Before making a protected request, fetch the CSRF token: ```typescript // Fetch CSRF token const response = await fetch('/api/csrf', { credentials: 'include' }); const { csrfToken } = await response.json(); // Use token in POST request await fetch('/api/your-endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken // Include token in header }, credentials: 'include', // Important: send cookies body: JSON.stringify(data) }); ``` ### Step 3: What Happens Automatically When `withCsrf()` wraps your handler: 1. Extracts CSRF token from `X-CSRF-Token` header 2. Extracts CSRF cookie from request 3. Verifies token matches cookie using HMAC 4. Clears token after validation (single-use) 5. If valid → calls your handler 6. If invalid → returns HTTP 403 Forbidden ## Complete Example: Protected Contact Form ```typescript // app/api/contact/route.ts import { NextRequest, NextResponse } from 'next/server'; import { withCsrf } from '@/lib/withCsrf'; import { withRateLimit } from '@/lib/withRateLimit'; import { validateRequest } from '@/lib/validateRequest'; import { contactFormSchema } from '@/lib/validation'; import { handleApiError } from '@/lib/errorHandler'; async function contactHandler(request: NextRequest) { try { const body = await request.json(); // Validate input const validation = validateRequest(contactFormSchema, body); if (!validation.success) { return validation.response; } const { name, email, subject, message } = validation.data; // Process contact form await sendEmail({ to: 'admin@example.com', from: email, subject, message }); return NextResponse.json({ success: true }); } catch (error) { return handleApiError(error, 'contact-form'); } } // Apply both rate limiting AND CSRF protection export const POST = withRateLimit(withCsrf(contactHandler)); export const config = { runtime: 'nodejs', }; ``` ## Frontend Integration Example ```typescript // components/ContactForm.tsx 'use client'; import { useState } from 'react'; export function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', subject: '', message: '' }); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); try { // 1. Fetch CSRF token const csrfRes = await fetch('/api/csrf', { credentials: 'include' }); const { csrfToken } = await csrfRes.json(); // 2. Submit form with token const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, credentials: 'include', body: JSON.stringify(formData) }); if (response.ok) { alert('Message sent successfully!'); setFormData({ name: '', email: '', subject: '', message: '' }); } else if (response.status === 403) { alert('Security validation failed. Please refresh and try again.'); } else if (response.status === 429) { alert('Too many requests. Please wait a moment.'); } else { alert('Failed to send message. Please try again.'); } } catch (error) { console.error('Error:', error); alert('An error occurred. Please try again.'); } } return (
setFormData({ ...formData, name: e.target.value })} required /> setFormData({ ...formData, email: e.target.value })} required /> setFormData({ ...formData, subject: e.target.value })} required />