War Story: Our Next.js 16 App Was Hit by a CSRF Attack via a Missing Middleware
It was 2 AM on a Tuesday when our on-call engineer pinged the team Slack: âWeâre seeing a spike in unauthorized project deletions and settings changes. User sessions are valid, but the requests donât match user behavior.â We had just launched our Next.js 16 project management SaaS two weeks prior, and this was our first major incident. None of us expected the root cause to be a missing 10-line middleware file.
The Setup: Our Next.js 16 Stack
We built the app using Next.js 16âs App Router, with NextAuth.js v5 for authentication, Prisma as our ORM, and Vercel for hosting. We followed most security best practices: hashed passwords, rate limiting on auth endpoints, input validation on all API routes. But in a pre-launch refactor, we stripped out our custom CSRF middleware to debug a conflicting cookie issue, and never added it back. We assumed SameSite=Lax cookies (the default for NextAuth) would be enough to block cross-site request forgery attacks. We were wrong.
The Attack: How It Happened
CSRF attacks exploit the browserâs automatic cookie-sending behavior. If a user is logged into our app, any request to our domain from a malicious site will include their valid session cookie. Our API routes for mutating data (delete project, update settings) only checked for a valid session, not a CSRF token. An attacker hosted a malicious form on a phishing site that submitted a POST request to our /api/project/delete endpoint. When logged-in users visited the phishing site, their browsers sent the request with their valid session, and we processed it without question.
We didnât notice at first because the attacker targeted low-traffic accounts initially. By the time we caught it, 14 users had had settings changed, and 3 had lost project data. The kicker? The attacker didnât even need to bypass our auth, they just used our own session handling against us.
The Fix: Adding CSRF Middleware in Next.js 16
We immediately rolled back to the last known good deployment, then set to work adding CSRF protection. For Next.js 16, we used the next-csrf package, which integrates seamlessly with the App Routerâs middleware. Hereâs the steps we took:
- Installed
next-csrfand initialized it with a secret stored in environment variables. - Added a CSRF token generation endpoint at
/api/csrfthat sets a signed CSRF cookie. - Updated all mutating API routes to check for a valid CSRF token in the request body or headers, rejecting requests without one.
- Added middleware that runs on all POST, PUT, DELETE, and PATCH requests to verify the CSRF token against the signed cookie.
We also audited all existing API routes to ensure no mutating endpoints were unprotected. Within 4 hours, we had a patched version deployed, and the malicious requests stopped immediately.
Lessons Learned: Preventing CSRF in Next.js 16
This incident taught us three key lessons for Next.js apps:
- Never rely solely on SameSite cookies for CSRF protection. SameSite=Lax blocks cross-site POST requests from some contexts, but itâs not foolproof, especially for older browsers or edge cases.
- Always use CSRF middleware for any mutating API routes. Itâs a small addition that blocks the vast majority of CSRF attacks.
- Add CSRF checks to your pre-launch security audit. We had a checklist, but CSRF wasnât on it, and that oversight cost us user trust.
We also set up automated security scans to catch missing middleware in future deployments. A missing 10-line file caused a major incident, but itâs a mistake weâll never make again.
