Back to Blog
| 3 min read | Advanced

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.

React Next.js

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:

  1. 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

#System Design #Next.js #Advanced #.NET
Casey Spaulding
Casey Spaulding

.NET Full Stack Engineer. 21 years in the Navy, then software.

Ask Casey AI

Ask Me Anything

Hi! I'm Casey's AI assistant. Ask me anything about his work, skills, or projects!

10 messages remaining