DEV Community

Cover image for I Got the proxy.ts Matcher Wrong for Three Projects Before I Understood Why

I Got the proxy.ts Matcher Wrong for Three Projects Before I Understood Why

Shubhra Pokhariya on June 17, 2026

A few days ago I published a post about the three-layer auth model and the invoice incident that made me rebuild how I think about Next.js 16 auth....
Collapse
 
merbayerp profile image
Mustafa ERBAY

Excellent breakdown.

What stood out to me is that the matcher issue is really a trust-boundary issue in disguise. The moment authorization logic depends on propagated identity data, every path that can bypass the propagation layer becomes part of the security model.
One thing I’m curious about: how do you approach the transition from role-based authorization to fine-grained permission models as applications grow? I’ve found that matcher gaps are usually discovered quickly, but authorization complexity tends to emerge gradually as roles multiply and business rules become more granular.

Great example of why security architecture is often more about assumptions than code.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks for the close read. You're right on both.

The matcher gap is a trust boundary issue, full stop. That's why I moved away from trusting forwarded headers and verify the JWT from the cookie directly in the Server Component. Verifying twice feels wasteful, but it's the only way to close the hole. Proxy is fast, not authoritative.

On roles to permissions: keep roles in the JWT for the proxy, resolve permissions from the DB in the Server Component. Permissions change too often to bake into a token. The proxy checks roles, page checks permissions, data layer scopes every query by userId. That's the only layer that can't be misconfigured into not running.

The proxy is a fast gate. The security boundary is the data layer. Everything else is defense in depth.

Collapse
 
merbayerp profile image
Mustafa ERBAY

That makes a lot of sense.

I especially like the distinction between the proxy as a fast gate and the data layer as the real security boundary. In many systems, people try to make the first auth layer carry too much responsibility. It works until one route, one rewrite, or one server action escapes that layer. Keeping roles in the token for quick routing decisions, resolving permissions closer to the page, and enforcing ownership at the data layer feels like the right separation of responsibilities.In the end, every layer can reduce risk, but the data layer is where authorization mistakes either become harmless or become incidents.

Collapse
 
hayrullahkar profile image
Hayrullah Kar

This is a goldmine of production insights. Transitioning from middleware.ts to proxy.ts in Next.js 16 isn't just a rename; it's a fundamental architectural shift.

You hit the nail on the head regarding the header trust boundary risk. If a developer forgets to update the matcher, a malicious user spoofing the x-user-id header on an unmatched route bypasses the gate completely. Your strategy of implementing double-verification (validating the token inside Server Components with getVerifiedUser) is an exceptional fix. It cleanly detaches data safety from edge-case matcher gaps.

Also, pointing out that orphaned middleware.ts files compile cleanly but fail silently in production is the exact type of hard-earned debugging advice that makes Dev.to posts invaluable. Fantastic work on prioritizing a bulletproof zero-trust model!

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Appreciate it. The orphaned middleware.ts thing was especially painful. CI passed, build passed, everything green. Then a redirect just didn't fire in staging. Took way too long to trace back to a file that was literally sitting there doing nothing.

The double-verification felt wrong at first. Like I was being inefficient. But once I saw someone could just send their own x-user-id on a missed route, it became non-negotiable. I'd rather verify twice than debug once.

Collapse
 
hayrullahkar profile image
Hayrullah Kar

I'd rather verify twice than debug once" is going on my wall. Exactly this. Those silent bypasses in staging are where developer sanity goes to die. Glad the breakdown resonated, cheers!

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Nice work debugging Next.js 16, as always! 😄

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Thanks. Learned a lot on this one.

Collapse
 
itskondrat profile image
Mykola Kondratiuk

ran into this - had a matcher pattern that missed a route, middleware just never fired. didn't catch it until staging. the part that gets me is how long it can sit there undetected.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

That's exactly the failure mode I covered. I got burned by it with /reports. Built fine, tests green, nothing in the logs. QA found it two weeks later by accident. Now it's just a checkbox on every PR. New route, verify the pattern, verify ROLE_ROUTES. Took two times before I stopped assuming I'd remember.

Collapse
 
slotkdev profile image
SlotKit

The header trust boundary point is the one most people miss until it bites them.
This gets worse in multi-tenant setups, where x-user-id alone isn't enough
you also need x-tenant-id propagated and verified independently, because a matcher gap there doesn't just leak permissions, it can let a request resolve against the wrong tenant's data entirely. Re-verifying both at the Server Component level becomes non-negotiable once you're not on a single-tenant assumption

Collapse
 
shubhradev profile image
Shubhra Pokhariya

Good point. Multi-tenant makes the header trust boundary go from "annoying bug" to "actual incident" real fast. x-tenant-id from an untrusted header hitting the wrong tenant's data is exactly the same class of problem, just higher stakes. Re-verifying both from the cookie at the Server Component level is the same fix, just with more fields. The pattern holds: proxy forwards for convenience, Server Component verifies for authority.

Collapse
 
multita profile image
Multita

The header trust boundary example is something I hadn't really considered before, especially how a matcher gap can turn forwarded headers into a security risk. The "fast gate, not the source of truth" mindset for proxy.ts makes a lot of sense.

Collapse
 
shubhradev profile image
Shubhra Pokhariya

The matcher gap thing is exactly the kind of bug that looks impossible until you see it once. After that you stop trusting any header you didn't verify yourself.

The "fast gate" framing took me too long to accept. I kept wanting the proxy to do more than it can. It redirects unauthenticated users well, but that's routing, not authorization. The real check happens where the data lives.

Collapse
 
sloan profile image
Sloan the DEV Moderator

Hey, this article appears to have been generated with the assistance of ChatGPT or possibly some other AI tool.

We allow our community members to use AI assistance when writing articles as long as they abide by our guidelines. Please review the guidelines and edit your post to add a disclaimer.

Failure to follow these guidelines could result in DEV admin lowering the score of your post, making it less visible to the rest of the community. Or, if upon review we find this post to be particularly harmful, we may decide to unpublish it completely.

We hope you understand and take care to follow our guidelines going forward!