Skip to content

Latest commit

 

History

History
347 lines (247 loc) · 8.69 KB

File metadata and controls

347 lines (247 loc) · 8.69 KB

CSRF Protection Implementation

Overview

JudgeFinder Platform implements CSRF (Cross-Site Request Forgery) protection using the double-submit cookie pattern to prevent unauthorized state-changing operations.

Security Model

What is CSRF?

CSRF attacks trick authenticated users into executing unwanted actions by exploiting their active session. Without CSRF protection, an attacker could create a malicious website that submits forms to JudgeFinder on behalf of logged-in users.

Protection Strategy

We use the double-submit cookie pattern:

  1. Server generates a cryptographically secure token
  2. Token is sent to client in both:
    • HTTP cookie (csrf-token)
    • Response headers (for debugging)
  3. Client includes token in X-CSRF-Token header on state-changing requests
  4. Server validates that cookie token matches header token

This works because:

  • Cookies are sent automatically by the browser
  • Headers must be set explicitly by JavaScript
  • Malicious sites can trigger requests but cannot read/set custom headers due to CORS

Protected Routes

Automatic Protection

All state-changing HTTP methods are protected:

  • POST
  • PUT
  • DELETE
  • PATCH

Safe methods are NOT protected (no CSRF risk):

  • GET
  • HEAD
  • OPTIONS

Protected Endpoints

/api/checkout/*        - Payment processing
/api/billing/*         - Subscription management
/api/user/*            - User settings
/api/admin/*           - Admin operations (also has API key auth)

Exempt Routes

The following routes are exempt from CSRF protection:

  1. Webhooks (/api/webhooks/*)

    • Use cryptographic signature verification instead
    • Stripe, Coinbase, etc. webhooks
  2. Cron Jobs (/api/cron/*)

    • Protected by CRON_SECRET API key
    • Called by Netlify scheduler, not browsers
  3. API Key Authenticated Routes (/api/admin/*)

    • When request includes valid X-API-Key header
    • CSRF not applicable for non-browser clients

Client Implementation

Automatic (Recommended)

Use the fetchWithCSRF wrapper for all state-changing requests:

import { fetchWithCSRF } from '@/lib/security/csrf-client'

// Automatically includes CSRF token
const response = await fetchWithCSRF('/api/checkout/adspace', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})

Manual Implementation

For custom fetch implementations:

import { getCSRFToken } from '@/lib/security/csrf-client'

const token = getCSRFToken()

const response = await fetch('/api/billing/subscription/cancel', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token || '',
  },
  body: JSON.stringify({ reason: 'user_request' }),
})

React Hook

For React components that need token access:

'use client'
import { useCSRFToken } from '@/lib/security/csrf-client'

export function MyComponent() {
  const csrfToken = useCSRFToken()

  const handleSubmit = async () => {
    if (!csrfToken) {
      console.error('CSRF token not available')
      return
    }

    // Use token in request...
  }
}

Server Implementation

Middleware (Automatic)

CSRF protection is enforced in /middleware.ts:

// Runs before all routes
const csrfError = enforceCSRFProtection(request)
if (csrfError) {
  return csrfError // 403 Forbidden
}

API Routes (Manual Check)

For additional validation in specific routes:

import { validateCSRFToken } from '@/lib/security/csrf'

export async function POST(request: NextRequest) {
  // Validate CSRF token
  if (!validateCSRFToken(request)) {
    return NextResponse.json({ error: 'CSRF validation failed' }, { status: 403 })
  }

  // Process request...
}

Exempting Routes

To exempt a route from CSRF protection, add pattern to /lib/security/csrf.ts:

const CSRF_EXEMPT_PATTERNS = [/^\/api\/webhooks\//, /^\/api\/cron\//, /^\/api\/your-exempt-route\//]

Token Lifecycle

Generation

  • Algorithm: Web Crypto API crypto.getRandomValues()
  • Length: 32 bytes (64 hex characters)
  • Entropy: 256 bits of cryptographic randomness

Storage

  • Cookie Name: csrf-token
  • HttpOnly: false (must be readable by JavaScript)
  • Secure: true (production only, HTTPS required)
  • SameSite: strict (maximum CSRF protection)
  • Max-Age: 24 hours

Expiration

Tokens expire after 24 hours. Middleware automatically:

  1. Checks for existing valid token
  2. Generates new token if missing or expired
  3. Sets cookie on all responses

Error Responses

CSRF Validation Failure

{
  "error": "CSRF validation failed",
  "code": "CSRF_TOKEN_INVALID",
  "message": "Invalid or missing CSRF token. Please refresh the page and try again."
}

Status Code: 403 Forbidden

Token Not Found

{
  "error": "CSRF token not found",
  "code": "CSRF_TOKEN_NOT_FOUND",
  "message": "No CSRF token found in cookies. Please refresh the page."
}

Status Code: 404 Not Found

Security Considerations

Constant-Time Comparison

Token validation uses constant-time string comparison to prevent timing attacks:

function constantTimeEqual(a: string, b: string): boolean {
  let result = 0
  for (let i = 0; i < a.length; i++) {
    result |= a.charCodeAt(i) ^ b.charCodeAt(i)
  }
  return result === 0
}

Defense in Depth

CSRF protection is one layer in our security model:

  1. CSRF Tokens - Prevent unauthorized state changes
  2. SameSite Cookies - Block cross-site cookie transmission
  3. CORS Headers - Restrict cross-origin requests
  4. Authentication - Verify user identity (Clerk)
  5. Authorization - Verify user permissions (RBAC)
  6. Rate Limiting - Prevent abuse (Upstash Redis)

Known Limitations

  1. Sub-domain Attacks: SameSite=strict prevents attacks from subdomains
  2. XSS Vulnerabilities: If XSS exists, attacker can read token. Mitigate with CSP.
  3. Man-in-the-Middle: Requires HTTPS in production (enforced)

Testing

Manual Testing

  1. GET Request (should succeed):

    curl https://judgefinder.io/api/judges/list
  2. POST without token (should fail with 403):

    curl -X POST https://judgefinder.io/api/checkout/adspace \
      -H "Content-Type: application/json" \
      -d '{"data":"test"}'
  3. POST with token (should succeed):

    # Get token from browser cookies
    TOKEN="your-csrf-token-here"
    
    curl -X POST https://judgefinder.io/api/checkout/adspace \
      -H "Content-Type: application/json" \
      -H "X-CSRF-Token: $TOKEN" \
      -H "Cookie: csrf-token=$TOKEN" \
      -d '{"data":"test"}'

Automated Testing

import { validateCSRFToken, generateCSRFToken } from '@/lib/security/csrf'

describe('CSRF Protection', () => {
  it('should reject requests without CSRF token', () => {
    const request = new Request('https://example.com/api/test', {
      method: 'POST',
    })
    expect(validateCSRFToken(request)).toBe(false)
  })

  it('should accept requests with valid CSRF token', () => {
    const token = generateCSRFToken()
    const request = new Request('https://example.com/api/test', {
      method: 'POST',
      headers: {
        'X-CSRF-Token': token,
        Cookie: `csrf-token=${token}`,
      },
    })
    expect(validateCSRFToken(request)).toBe(true)
  })
})

Troubleshooting

"CSRF validation failed" in production

  1. Check that client is sending X-CSRF-Token header
  2. Verify cookie is set: Open DevTools → Application → Cookies → csrf-token
  3. Ensure request includes credentials: credentials: 'include' in fetch
  4. Check CORS headers allow custom headers

Token not set on localhost

  1. Verify middleware is running: Check Network tab for CSRF cookie
  2. Clear browser cookies and refresh
  3. Check that NODE_ENV is set correctly

Webhooks failing with 403

  1. Verify route is in CSRF_EXEMPT_PATTERNS
  2. Check webhook uses signature verification instead
  3. Ensure webhook doesn't include CSRF token accidentally

Compliance

This implementation satisfies:

  • OWASP Top 10 - A01:2021 Broken Access Control
  • OWASP CSRF Prevention Cheat Sheet - Double Submit Cookie Pattern
  • PCI DSS 6.5.9 - Protection against CSRF attacks
  • NIST SP 800-63B - Session management best practices

References