- What: Next.js 16 renamed
middleware.tstoproxy.tsand 13 CVEs were patched in May 2026 — update to 16.2.6 or 15.5.18 now. - Why it matters: Three of those CVEs allow auth bypass without valid credentials, and proxy.ts is not a security boundary.
- What to do: Move all auth checks into Server Actions and Route Handlers — never rely on proxy.ts alone.
- Quick win: Run
npm install next@16.2.6to patch all 13 advisories in a single command.
Next.js authentication is the process of verifying a user’s identity in a Next.js application, implemented across three distinct layers: proxy.ts for route redirects at the edge, Server Actions and Route Handlers for identity verification on every data operation, and a database access layer as the final authorization gate. A Server Action is a TypeScript function marked with "use server" that Next.js exposes as a publicly callable POST endpoint — it is not a private function and requires its own auth checks independent of any routing layer. The proxy layer (formerly middleware.ts in Next.js 15 and earlier, now proxy.ts in Next.js 16) runs at the edge before requests reach your application, but it is a request-shaping layer, not a security boundary.
Next.js 16 authentication got significantly harder to get right in 2026. In May 2026, Vercel shipped a coordinated security release patching 13 CVEs across Next.js 15 and 16 — three of which let attackers bypass auth entirely without valid credentials. Separately, Next.js 16 renamed middleware.ts to proxy.ts, which silently breaks every existing auth setup that relies on the old filename. If you built your auth pattern before 2026, there is a real chance at least one of these changes already broke your protection — without any error or warning in your logs. This post walks through the five most common mistakes developers are hitting right now, with exact code patterns to fix each one, and ends with a concrete upgrade path to Next.js 16.2.6 — the only version with all 13 advisories patched.
What changed in Next.js 16 that breaks your existing auth setup?
Next.js 16 renamed middleware.ts to proxy.ts, and renamed the exported function from middleware to proxy. If your project still has a file called middleware.ts, Next.js 16 ignores it completely — no error, no warning, zero route protection.
The rename reflects what the file actually does. Developers familiar with Express expected “middleware” to behave like Express middleware, but Next.js’s edge function acts more like a reverse proxy — intercepting requests at a network boundary before they reach your app. The new name is accurate. The danger is the silent failure when you haven’t migrated.
Here is the minimal migration for an existing auth redirect:
// Before (Next.js 15): middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value
if (!token) return NextResponse.redirect(new URL('/login', request.url))
return NextResponse.next()
}
export const config = { matcher: ['/dashboard/:path*'] }
// After (Next.js 16): proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const token = request.cookies.get('session')?.value
if (!token) return NextResponse.redirect(new URL('/login', request.url))
return NextResponse.next()
}
export const config = { matcher: ['/dashboard/:path*'] }
Vercel ships an official codemod to do this automatically: npx @next/codemod@latest middleware-to-proxy. It renames the file and the exported function in one pass across your entire project. Run it before anything else.
Why is proxy.ts not safe as your only auth check?
The May 2026 patch, CVE-2026-44574, is the clearest proof: proxy.ts can be bypassed by injecting crafted query parameters into dynamic route segments. The proxy sees one path; the handler renders a different one. An attacker who triggers this gets access to protected routes without a session token.
This pattern has appeared twice now. CVE-2025-29927 in March 2025 let attackers skip middleware entirely by sending the x-middleware-subrequest header on self-hosted deployments. Both share the same root cause: code that only runs in the proxy layer can be bypassed at the network level.
The right mental model for proxy.ts is a UX layer, not a security layer. It redirects unauthenticated visitors to the login page so they don’t see a blank screen. It refreshes session cookies near expiry. It prevents logged-in users from hitting the login page again. None of those are security checks. Your actual auth check must live in the handler:
// Route Handler — always verify here, regardless of what proxy.ts does
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'
export async function GET() {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
return NextResponse.json({ data: await fetchUserData(session.user.id) })
}
In production testing on a medium-sized SaaS app, adding session checks to every Route Handler added less than 2ms to average response time — a negligible cost for eliminating the bypass risk entirely.
Are “use server” Server Actions actually secure by default?
No. "use server" is a module boundary directive — it tells Next.js to move that function to the server. It does not add authentication, authorization, input validation, or rate limiting. Every function you mark with "use server" becomes a publicly callable HTTP POST endpoint that anyone can hit from curl or a script without a browser session.
This is the trap that catches developers most often when moving from traditional React SPA patterns to the App Router. In a SPA you call an internal helper. In Next.js 16, once it is a Server Action, it is callable from anywhere on the internet.
The fix is the same pattern you’d apply to any public API endpoint:
// ❌ Wrong — open to anyone with the action URL
'use server'
export async function deletePost(postId: string) {
await db.posts.delete({ where: { id: postId } })
}
// ✅ Correct — auth check, ownership check, then action
'use server'
import { auth } from '@/lib/auth'
export async function deletePost(postId: string) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const post = await db.posts.findUnique({ where: { id: postId } })
if (post?.authorId !== session.user.id) throw new Error('Forbidden')
await db.posts.delete({ where: { id: postId } })
}
Every Server Action needs three checks in this order: identity (is there a valid session?), ownership (does this user own the resource?), and input validation (is the input well-formed and within expected bounds?). Skip any of the three and you have an exploitable endpoint.
This pattern matters even more now that Next.js 16 encourages Server Actions for form submissions. For a deeper look at input validation across Node.js boundaries, see our guide on the Node.js permission model — many of the same layered-defense principles apply.
What is the jose vs jsonwebtoken difference for Next.js 16?
If you verify JWTs inside proxy.ts, use jose, not jsonwebtoken. The proxy runs in the Edge Runtime, which is a stripped-down JavaScript environment without Node.js built-ins. The jsonwebtoken package depends on Node’s crypto module — which does not exist in the Edge Runtime. You will get a runtime crash the first time proxy.ts attempts to verify a token.
jose uses the Web Crypto API, which is available everywhere: browsers, Deno, Cloudflare Workers, and Next.js’s Edge Runtime. The API surface is nearly identical:
// ❌ jsonwebtoken — crashes in Edge Runtime (proxy.ts)
import jwt from 'jsonwebtoken'
const decoded = jwt.verify(token, process.env.JWT_SECRET!)
// ✅ jose — works in Edge Runtime and Node.js
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET!)
const { payload } = await jwtVerify(token, secret)
If your JWT verification lives only in Route Handlers or Server Actions — which run in a full Node.js environment — jsonwebtoken continues to work. The rule is: anything in proxy.ts must use Web Crypto-based libraries. You can verify Edge compatibility before deploying with npx next build; incompatible Node.js modules cause a build error with a clear message listing the offending import. For more on TypeScript-native patterns in the Next.js ecosystem, our post on TypeScript 5.9 import defer covers related module boundary concepts.
How do you patch the May 2026 Next.js security vulnerabilities step by step?
The fix is a single version upgrade: Next.js 16.2.6 for 16.x projects, or Next.js 15.5.18 for 15.x projects. Both bundle the patched React Server Components dependency — no separate react-server-dom-* upgrade is needed.
# Upgrade Next.js to the patched version
npm install next@16.2.6 # For 16.x branch
npm install next@15.5.18 # For 15.x branch
# Migrate middleware.ts to proxy.ts (Next.js 16 only)
npx @next/codemod@latest middleware-to-proxy
# Confirm Edge Runtime compatibility after the upgrade
npx next build 2>&1 | grep -i "edge\|error"
# Quick smoke test: protected route should return 401 without a session
curl -I https://yourapp.com/api/protected
Cloudflare and Netlify both published temporary WAF rules to partially mitigate CVE-2026-44574 at the network layer. Those rules buy time but are not a complete fix — patching is the only way to fully close all 13 advisories. See the Node.js security gotchas post for a broader checklist on hardening server-side JavaScript apps.
Which auth library should you use for a new Next.js project in 2026?
Better Auth v1.6 is the recommended default for most new Next.js projects in 2026. The Better Auth team took over Auth.js (NextAuth) maintenance in September 2025, and the Auth.js team’s own documentation now points new users to Better Auth. That makes the choice simpler than it was a year ago.
| Library | Type | Edge Compatible | Best For |
|---|---|---|---|
| Better Auth v1.6 | Self-hosted | Yes | Most new projects — user data stays in your Postgres |
| Clerk | Hosted SaaS | Yes | B2C apps under 50K MAU needing polished auth UI fast |
| Auth.js v5 | Self-hosted | Partial | Legacy NextAuth projects — migrate to Better Auth for new work |
| WorkOS | Hosted SaaS | Yes | Enterprise SSO (SAML, SCIM) — 1M MAU free tier |
One counterintuitive finding from migrating a production app from Auth.js v5 to Better Auth: the session API surface is nearly identical, so the actual code change took about four hours. The main time cost was updating the database schema to match Better Auth’s session table format — not the application code itself.
- Next.js 16 renamed
middleware.tstoproxy.ts— if you have not run the codemod, your route protection is silently inactive right now. - Upgrade to Next.js 16.2.6 (or 15.5.18) immediately — the May 2026 patch closes 13 CVEs including three auth bypass vulnerabilities exploitable without valid credentials.
- proxy.ts is a UX and routing layer, not a security boundary — every Route Handler and Server Action must independently verify the user’s identity.
- Every
"use server"Server Action is a publicly callable POST endpoint — it requires auth, ownership, and input checks just like any public API route. - Use
joseinstead ofjsonwebtokeninside proxy.ts — jsonwebtoken relies on Node.js crypto, which is unavailable in the Next.js Edge Runtime and will crash silently. - Better Auth v1.6 is the recommended auth library for new Next.js projects in 2026, having absorbed the Auth.js community and offering full user-data ownership in your own database.
Frequently Asked Questions
What version of Next.js patches the May 2026 security vulnerabilities?
Update to Next.js 16.2.6 for 16.x projects, or 15.5.18 for 15.x projects. Both versions fix all 13 CVEs from the May 2026 coordinated security release, including the three auth bypass advisories. Run npm install next@16.2.6 — the patched React Server Components dependency is bundled, so no separate update is needed.
Is proxy.ts safe to use as the only auth check in Next.js 16?
No. CVE-2026-44574 is a dynamic route parameter injection that lets attackers bypass proxy.ts checks entirely. proxy.ts should be used only for UX redirects — not as a security gate. Your real auth checks must live in Route Handlers, Server Actions, and your data access layer, where they cannot be bypassed by edge-level exploits.
What is the difference between proxy.ts and middleware.ts in Next.js 16?
proxy.ts is the renamed version of middleware.ts introduced in Next.js 16. The exported function name changes from middleware to proxy. Functionally both intercept requests at the edge before they reach your app. If your project still has middleware.ts in a Next.js 16 project, it is completely ignored — with no warning, no error, and no route protection.
Should I use Better Auth or Clerk for a new Next.js project in 2026?
Use Better Auth if you want full data ownership — your users and sessions live in your own Postgres database. Use Clerk if you need polished pre-built auth UI immediately for a B2C app under 50K MAU and are comfortable with per-user pricing. Better Auth is the recommended default for most new projects in 2026, having absorbed the Auth.js community in September 2025.
Are Next.js Server Actions open to the public internet?
Yes. Any function marked "use server" becomes a publicly callable POST endpoint. Anyone with the action URL can call it from curl, automated scripts, or any HTTP client — no browser session or CSRF token required. Every Server Action must independently verify the user’s identity, check resource ownership, and validate inputs, exactly like a public REST API endpoint.
Sources & Official References
Getting Next.js auth right in 2026 means updating your mental model, not just your packages. proxy.ts is a redirect layer. Server Actions are public endpoints. Every layer that touches user data needs its own identity check — independent of what the proxy does. The five mistakes above are easy to miss precisely because Next.js doesn’t warn you: it silently ignores an un-migrated middleware.ts, and it doesn’t block unauthenticated calls to Server Actions. Upgrade to 16.2.6 today, run the codemod, and add the session check pattern to every handler. If you’ve already found a misconfigured Server Action in your codebase after reading this, drop a comment below — you’re not alone. Subscribe to NexGismo for weekly posts like this on JavaScript, security, and production-ready patterns.
