CSRF: Why AI Tools Skip This Critical Protection

CSRF tricks your browser into making requests you didn't intend. Next.js Server Actions are protected, but AI-generated Route Handlers typically aren't.

Quick Answer: CSRF (Cross-Site Request Forgery) tricks users into performing unwanted actions on sites where they're logged in. Next.js Server Actions have built-in protection, but custom API routes don't. AI tools typically generate vulnerable Route Handlers because CSRF protection isn't needed for code to "work."

What is CSRF?

CSRF (Cross-Site Request Forgery) is an attack where a malicious website tricks your browser into making requests to a legitimate site where you're authenticated. Your browser automatically includes your session cookies, so the target site thinks the request came from you.

Think of it like someone forging your signature on a check while you're logged into your bank. The bank sees your valid credentials and processes the transaction - even though you never intended to make it.

According to OWASP's A01:2021 Broken Access Control, CSRF falls under this category because it allows attackers to bypass access controls by exploiting authenticated user sessions.

How Do CSRF Attacks Work?

Here's a typical CSRF attack scenario:

  1. User logs into legitimate-bank.com and gets a session cookie
  2. User visits malicious-site.com (via phishing email or ad)
  3. Malicious site contains a hidden form that POSTs to legitimate-bank.com/transfer
  4. Form auto-submits when the page loads
  5. Browser includes the user's bank session cookies automatically
  6. Bank processes the transfer - user never knew it happened

The attack works because browsers automatically include cookies for any request to a domain, regardless of which site initiated the request. Per the OWASP CSRF attack documentation, this is why cookie-based authentication needs additional protection.

What Are Modern Browser Protections?

Modern browsers provide partial CSRF protection through SameSite cookies. According to MDN's SameSite documentation, here's how the settings work:

  • SameSite=Lax (browser default since 2020): Blocks cross-site POST requests with cookies. Allows GET requests for navigation.
  • SameSite=Strict: Blocks ALL cross-site requests with cookies. Most secure but can break legitimate flows.
  • SameSite=None: No protection. Must be paired with Secure flag (HTTPS only).

Important: SameSite=Lax helps but isn't complete protection. GET requests can still leak sensitive data, and subdomains may bypass restrictions. For sensitive operations in vibe coded apps, add explicit CSRF protection.

How Do Next.js Server Actions Handle CSRF?

According to Next.js documentation, Server Actions have built-in CSRF protection through multiple mechanisms:

  • POST-only: Server Actions only accept POST requests, preventing GET-based attacks
  • Origin validation: Validates the Origin header matches your domain
  • Encrypted action IDs: Action endpoints aren't enumerable or guessable
  • SameSite cookies: Works with default browser protections

Server Actions Are Safe

If you're using 'use server' functions in Next.js App Router, you don't need to add CSRF tokens. The framework handles it.

SECURE Server Action with built-in CSRF protection
// app/actions/transfer.ts
'use server'

import { auth } from '@/lib/auth'
import { revalidatePath } from 'next/cache'

export async function transferMoney(formData: FormData) {
  const session = await auth()
  if (!session?.user) {
    throw new Error('Unauthorized')
  }

  const amount = formData.get('amount') as string
  const toAccount = formData.get('toAccount') as string

  // Server Action has built-in CSRF protection
  // No additional tokens needed!
  await executeTransfer(session.user.id, toAccount, parseFloat(amount))

  revalidatePath('/account')
  return { success: true }
}

// Usage in component:
// <form action={transferMoney}>
//   <input name="amount" />
//   <input name="toAccount" />
//   <button type="submit">Transfer</button>
// </form>

Why Do AI Tools Generate Vulnerable Routes?

When you ask Cursor, Claude Code, or Bolt to "create an API endpoint," they generate Route Handlers, not Server Actions. Route Handlers are more flexible and familiar from Express.js patterns, but they lack Server Actions' built-in CSRF protection.

The problem: AI-generated code works without CSRF protection. You can test it, deploy it, and it functions correctly - until an attacker exploits it. Vibe coders often don't realize the gap because CSRF attacks don't surface during normal development.

This is different from missing authentication which fails obviously. CSRF vulnerabilities are silent - the code works, it just works for attackers too.

Vulnerable vs Secure: Route Handler Examples

VULNERABLE AI-generated Route Handler - no CSRF protection
// app/api/transfer/route.ts
// VULNERABLE: AI generates this without CSRF protection
import { NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'

export async function POST(request: Request) {
  const session = await getServerSession()
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { amount, toAccount } = await request.json()

  // Attacker can trigger this from malicious-site.com!
  // Browser will include session cookies automatically
  await transferMoney(session.user.id, toAccount, amount)

  return NextResponse.json({ success: true })
}
SECURE Route Handler with Origin validation
// app/api/transfer/route.ts
// SECURE: Validate Origin header
import { NextResponse } from 'next/server'
import { headers } from 'next/headers'
import { getServerSession } from 'next-auth'

const ALLOWED_ORIGINS = [
  process.env.NEXT_PUBLIC_APP_URL,
  'https://yourdomain.com'
].filter(Boolean)

export async function POST(request: Request) {
  const headersList = headers()
  const origin = headersList.get('origin')

  // CSRF Protection: Reject requests from unknown origins
  if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
  }

  const session = await getServerSession()
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { amount, toAccount } = await request.json()
  await transferMoney(session.user.id, toAccount, amount)

  return NextResponse.json({ success: true })
}

Option: CSRF Tokens with Middleware

For complex applications requiring stricter protection, implement CSRF tokens using middleware. The csrf-csrf library (not the deprecated csurf) provides a double-submit cookie pattern.

SECURE CSRF token middleware implementation
// middleware.ts
import { doubleCsrf } from 'csrf-csrf'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const { generateToken, validateRequest } = doubleCsrf({
  getSecret: () => process.env.CSRF_SECRET!,
  cookieName: '__Host-csrf',
  cookieOptions: {
    httpOnly: true,
    sameSite: 'strict',
    path: '/',
    secure: true,
  },
})

export async function middleware(request: NextRequest) {
  // Skip CSRF for Server Actions (built-in protection)
  if (request.headers.get('next-action')) {
    return NextResponse.next()
  }

  // Validate CSRF for mutating requests to API routes
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method)) {
    if (request.nextUrl.pathname.startsWith('/api/')) {
      const isValid = await validateRequest(request)
      if (!isValid) {
        return NextResponse.json(
          { error: 'Invalid CSRF token' },
          { status: 403 }
        )
      }
    }
  }

  return NextResponse.next()
}

// app/api/csrf/route.ts - Token endpoint
export async function GET() {
  const token = generateToken()
  return NextResponse.json({ token })
}

When to use tokens: Financial transactions, account deletion, admin operations, or any action with irreversible consequences. For most CRUD operations in vibe coded apps, Origin validation is sufficient.

When Should You Use Each Approach?

ApproachComplexityUse Case
Server ActionsLowMost mutations - forms, data updates. Built-in protection.
Origin ValidationLowRoute Handlers that must remain as APIs. Simple to add.
CSRF TokensMediumHigh-value operations, financial apps, compliance requirements.

Best practice for vibe coders: Use Server Actions for mutations whenever possible. They're simpler, safer, and optimized for Next.js. Only use Route Handlers when you need streaming, webhooks, or third-party integrations - and add Origin validation.

AI Fix Prompt for CSRF Vulnerabilities

Copy this prompt into Cursor or Claude Code to scan for CSRF vulnerabilities:

Frequently Asked Questions

What is CSRF and how does it work?

CSRF (Cross-Site Request Forgery) tricks your browser into making requests you didn't intend. An attacker creates a malicious page with a hidden form that submits to a legitimate site where you're logged in. Your browser automatically includes your cookies, so the request appears to come from you. Per OWASP, this allows attackers to perform actions using your authenticated session.

Do Next.js Server Actions need CSRF protection?

No, Server Actions have built-in CSRF protection. According to Next.js documentation, they enforce POST-only requests, validate Origin headers, use encrypted action IDs, and rely on SameSite cookies. You don't need to add CSRF tokens to Server Actions. However, custom Route Handlers (/api routes) do NOT have this protection and need manual implementation.

How do I add CSRF protection to my API routes?

For Next.js Route Handlers, you have three options: 1) Validate the Origin header against an allowlist, 2) Implement CSRF tokens using a library like csrf-csrf, or 3) Convert to Server Actions which have built-in protection. Origin validation is simplest for most cases. The OWASP CSRF Prevention Cheat Sheet recommends the Synchronizer Token Pattern for complex apps.

Does React protect against CSRF?

React itself doesn't protect against CSRF - that's a server-side concern. However, if you're using Next.js Server Actions, they have built-in CSRF protection. For custom API endpoints or other frameworks, you need to implement protection manually. Modern browsers' SameSite cookie defaults provide partial protection but aren't a complete solution.

Is SameSite cookie enough for CSRF protection?

SameSite=Lax (the browser default since 2020) provides partial CSRF protection by blocking cross-site POST requests that include cookies. However, it's not complete: GET requests can still leak data, subdomains may be trusted, and some scenarios bypass it. For sensitive operations like financial transactions, add explicit CSRF protection using tokens or Origin validation.

Related Vulnerabilities

External Resources

Scan Your Code for CSRF Vulnerabilities

vibeship scanner identifies Route Handlers without CSRF protection and recommends whether to add Origin validation, CSRF tokens, or convert to Server Actions. Get a security report in minutes.

Try vibeship scanner Free