Surviving CVE-2025-29927 — Why .NET Backend Auth Model Prevented a Major Vulnerability
March 2025 — A critical vulnerability (CVE-2025-29927) hit the Next.js ecosystem, allowing attackers to bypass middleware authentication using a forged `x-middleware-subrequest` header. If you’re using Next.js middleware for auth checks — your routes may have been wide open. .NET backend to the rescue.
March 2025 — A critical vulnerability (CVE-2025-29927)🛡️ hit the Next.js ecosystem, allowing attackers to bypass middleware authentication using a forged x-middleware-subrequest header. If you’re using Next.js middleware for auth checks — your routes may have been wide open.
But not all apps were equally vulnerable. Here's how I designed REIstacks — my real estate SaaS project — to resist this attack from day one using multi-layered, backend-driven authentication.
🔍 What Was CVE-2025-29927?
This vulnerability affected Next.js 11.1.4 through 15.2.2. It allowed requests to skip middleware execution (which often contains auth checks) just by sending a header like:
x-middleware-subrequest: middleware:middleware:middleware
Since middleware is often your "bouncer at the door", this basically let attackers sneak in through the back.
🧱 The Problem with Middleware-Only Auth Here’s what I’ve seen in the wild:
// middleware.ts
if (!request.cookies.get('auth_token')) {
return NextResponse.redirect('/login');
}
Seems fine... until the middleware gets skipped entirely due to a header exploit like this one.
If your API routes or server actions don’t verify sessions themselves — attackers could hit protected pages directly.
✅ How REIstacks Auth Works (Defense in Depth) Even before this vulnerability, I built REIstacks with layered security:
- Frontend: Next.js 15 with Middleware Handles subdomain routing (e.g. john.reistacks.com)
Blocks unauthenticated access to dashboard routes
NOW patched to block x-middleware-subrequest:
import { NextRequest, NextResponse } from 'next/server';
import * as Sentry from '@sentry/nextjs';
// ✅ [SECURITY PATCH] Block requests with x-middleware-subrequest header
if ( request.headers.has( 'x-middleware-subrequest' ) )
{
Sentry.captureMessage( `Blocked suspicious x-middleware-subrequest at ${ request.url }`, 'warning' );
console.warn( `[SECURITY] Blocked x-middleware-subrequest: ${ request.url }` );
return new NextResponse( 'Forbidden', { status: 403 } );
}
2. Backend: .NET Web API on Azure to the RESCUE
Verifies JWTs from HttpOnly secure cookies
Every protected route uses
[HttpPost("setup")]
[Authorize]
[EnableCors("ReistacksFrontend")]
public async Task<IActionResult> Setup([FromBody] OrganizationSetupRequest request)
{
Auth is NOT based on frontend headers — only validated tokens
3. Session Storage in SQL Refresh tokens are stored in the DB and rotated
Revoked tokens can’t be reused
Login and activity logs are audited
🧪 What If Someone Had Tried the Exploit?
They would’ve received a 403 Forbidden at the middleware layer.
If somehow bypassed?
They would have been met by the backend’s [Authorize] decorator and JWT verification logic — and still couldn’t access any data.
🔄 What Actually Happens in the Exploit Scenario (Before the Patch)
🧑💻 Attacker spoofs x-middleware-subrequest → middleware is skipped
🚪 They hit a protected route like /dashboard
📄 The dashboard page loads, but only the static page shell
📡 The page makes API requests (e.g. to /api/leads)
❌ No access_token cookie is present (attacker isn’t logged in)
🔐 Backend .NET API checks auth → rejects with 401 Unauthorized
🔒 No sensitive data is exposed
✅ Why This Happens
REIstacks sets JWTs in HttpOnly cookies, only after:
- Google login
- Backend token exchange
- Secure cookie set (Domain=.reistacks.com, HttpOnly, Secure, etc.)
So unless the attacker:
- Logged in via Google
- Got a valid token issued by the backend (and to do that they would have to have an account in the database)
- AND had the correct cookie for .reistacks.com
…they wouldn't have the access_token cookie, and the backend rejects them.
Backend Authorization
sequenceDiagram participant Attacker participant NextJS_Middleware participant Dashboard_Page participant Browser participant DotNet_API Note over Attacker: Sends spoofed request with<br/>x-middleware-subrequest header Attacker->>NextJS_Middleware: Request /dashboard with spoofed header Note over NextJS_Middleware: ⚠️ Skipped due to vulnerability NextJS_Middleware-->>Attacker: Allow access to /dashboard Attacker->>Dashboard_Page: Loads dashboard page (static shell) Dashboard_Page->>Browser: Runs JS to fetch data Browser->>DotNet_API: GET /api/leads<br/>(no access_token cookie) DotNet_API-->>Browser: ❌ 401 Unauthorized Note over DotNet_API: JWT missing or invalid Note over Attacker: No sensitive data returned
So yeah — the REIstacks API wasn’t fooled. 😎
🧠 The Lesson: Do Auth on the Backend
Here’s what this vulnerability teaches us:
- ✅ Middleware is not security — it’s just routing logic
- ✅ Tokens must be verified server-side — every time
- ✅ Cookies should be
HttpOnly+Secure+SameSite - ✅ Use activity logs and refresh token storage
- ✅ Defense in depth wins — every layer matters
🛠️ How to Actually Patch This
A correction worth stating plainly: you cannot fix this from inside middleware.ts. The vulnerability works by making Next.js skip middleware execution entirely, so a header check placed there never runs for an exploited request.
Block the header before it reaches Next.js — at your reverse proxy or CDN:
# strip the forged header at the edge
proxy_set_header x-middleware-subrequest "";
And upgrade. The fix landed in 15.2.3, and was backported to 14.2.25, 13.5.9 and 12.3.5 — check the line you are actually on rather than assuming you need 15.x.
🔐Final Thoughts
I didn't expect REIstacks' backend-first auth system to be put to the test so soon. But CVE-2025-29927 proved why zero-trust, backend-enforced, cookie-based authentication is the way to go for multi-tenant SaaS platforms.
If you're building apps in Next.js — learn from this.
Never rely solely on middleware or client state for authentication. Let the server do the real checking.
Stay safe out there. 🧠
📚 References
Tags
.NET Full Stack Engineer. 21 years in the Navy, then software.