CSRF in Next.js Applications
Next.js Server Actions include automatic CSRF protection via origin checking, but API routes have no built-in CSRF defense. Applications that use cookie-based authentication with API routes are particularly vulnerable, and misconfigured CORS policies can weaken even Server Action protections.
Scan Your Next.js AppHow CSRF Manifests in Next.js
Next.js API routes (/api/*) are vulnerable to CSRF when they use cookie-based authentication (such as next-auth session cookies). A malicious website can make authenticated requests to your API if the user is logged in. Server Actions in Next.js 14+ check the Origin header by default, providing baseline CSRF protection. However, this can be circumvented if CORS headers are misconfigured to allow arbitrary origins. Form submissions that POST to API routes without CSRF tokens are the most common vector. Next.js does not include a CSRF token mechanism for API routes.
Real-World Impact
A Next.js banking application used API routes for transfers with cookie-based authentication. An attacker created a page with a hidden form that submitted a transfer request to the victim's bank. When the victim visited the attacker's page while logged into their bank, the transfer executed automatically.
Step-by-Step Fix
Use Server Actions for mutations
Server Actions automatically validate the Origin header, providing CSRF protection.
// app/actions.ts
'use server'
export async function updateProfile(formData: FormData) {
const session = await getSession();
if (!session) throw new Error('Unauthorized');
await db.user.update({
where: { id: session.userId },
data: { name: formData.get('name') as string },
});
}
// app/profile/page.tsx
import { updateProfile } from '../actions';
export default function Profile() {
return (
<form action={updateProfile}>
<input name="name" />
<button type="submit">Update</button>
</form>
);
}Add CSRF tokens to API routes
Use edge-csrf for CSRF protection on API routes.
// middleware.ts
import { createCsrfMiddleware } from '@edge-csrf/nextjs';
const csrfProtect = createCsrfMiddleware({
cookie: {
secure: process.env.NODE_ENV === 'production',
},
});
export async function middleware(request: NextRequest) {
const csrfError = await csrfProtect(request);
if (csrfError) {
return new NextResponse('CSRF validation failed', { status: 403 });
}
}Configure SameSite cookies
Set SameSite attribute on session cookies to prevent cross-origin requests.
// For next-auth
export const authOptions: NextAuthOptions = {
cookies: {
sessionToken: {
name: 'next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
},
},
},
};Validate Origin header in API routes
Check the Origin header matches your domain for state-changing requests.
// app/api/transfer/route.ts
import { NextRequest, NextResponse } from 'next/server';
const ALLOWED_ORIGINS = ['https://yourdomain.com'];
export async function POST(req: NextRequest) {
const origin = req.headers.get('origin');
if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
return NextResponse.json(
{ error: 'CSRF validation failed' },
{ status: 403 }
);
}
// Process the request
}Prevention Best Practices
1. Use Server Actions instead of API routes for mutations - they include Origin header checking. 2. For API routes, implement CSRF token validation using a library like csrf or edge-csrf. 3. Use SameSite=Lax or Strict on session cookies. 4. Validate the Origin and Referer headers on mutation endpoints. 5. Never rely solely on cookie-based auth for API routes without CSRF protection.
How to Test
1. Create an HTML file on a different domain with a form that POSTs to your API route. 2. Log in to your app, then open the HTML file - check if the request succeeds. 3. Check if API routes validate Origin/Referer headers. 4. Verify session cookies have SameSite attribute set. 5. Use Vibe App Scanner to detect missing CSRF protections in your Next.js application.
Frequently Asked Questions
Do Next.js Server Actions have CSRF protection?
Yes. Server Actions automatically check the Origin header against the Host header to prevent cross-origin requests. This provides baseline CSRF protection. However, API routes do not have this protection and need manual CSRF defense.
Is SameSite=Lax enough to prevent CSRF in Next.js?
SameSite=Lax prevents CSRF for POST requests from cross-origin sites, which covers most attack scenarios. However, it allows GET requests with cookies, so ensure GET endpoints do not perform state changes. SameSite=Strict provides stronger protection but may break OAuth flows.
Do I need CSRF protection for JWT-based auth?
If JWTs are stored in localStorage and sent via Authorization headers, CSRF is not a concern because cross-origin requests cannot access localStorage. However, if JWTs are stored in cookies (common for SSR), you still need CSRF protection.
Related Security Resources
Is Your Next.js App Vulnerable to CSRF?
VAS automatically scans for csrf vulnerabilities in Next.js applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Next.js stack.