All Docs
FeaturesMaking Tax DigitalUpdated March 20, 2026

Security Fix: Closing the tRPC Middleware Authentication Gap

Security Fix: Closing the tRPC Middleware Authentication Gap

Release v1.0.436 · SOC2-03 Compliance


Background

This platform enforces authentication at multiple layers. For most routes, src/middleware.ts acts as the outermost gate — redirecting unauthenticated requests before they reach application logic. Inside tRPC, individual procedures are further protected by declaring them as protectedProcedure, which checks for a valid session before executing any handler.

These two layers are designed to be complementary. The middleware provides a catch-all safety net; protectedProcedure provides per-procedure enforcement.

The Gap

Prior to this release, /api/trpc was listed explicitly in the isPublicRoute array inside src/middleware.ts:

// src/middleware.ts (before fix)
const isPublicRoute = createRouteMatcher([
  '/login',
  '/api/auth(.*)',
  '/api/trpc',   // ← this bypassed the outermost auth layer
]);

This meant the middleware unconditionally allowed all requests to every tRPC endpoint through without evaluating session state. Authentication was deferred entirely to the tRPC layer.

Why this matters

Relying solely on protectedProcedure is workable in practice, but it creates a fragile single point of failure. If a developer defines a procedure as publicProcedure — even by mistake, or during rapid prototyping — that endpoint is immediately reachable by anyone on the internet with no middleware interception.

In a platform handling HMRC OAuth tokens, encrypted bank credentials, and taxpayer financial data, that risk is unacceptable under SOC2-03 controls.

The Fix

/api/trpc has been removed from the isPublicRoute list. The middleware no longer pre-emptively marks tRPC requests as public.

// src/middleware.ts (after fix)
const isPublicRoute = createRouteMatcher([
  '/login',
  '/api/auth(.*)',
  // /api/trpc is no longer listed here
]);

Critically, this does not break tRPC functionality. The Auth.js middleware wrapper still processes every request and attaches the session to req.auth. Authenticated users experience no change. Unauthenticated requests to protectedProcedure endpoints will now be caught at both the middleware layer and the tRPC layer.

Defence in Depth

With this change, the authentication architecture now operates as intended:

LayerMechanismCatches
Middleware (src/middleware.ts)Auth.js session checkAll unauthenticated requests to /api/trpc
tRPC procedureprotectedProcedureUnauthenticated calls that reach procedure handlers

Neither layer alone is sufficient. Together they ensure that a misconfigured procedure cannot silently become a public endpoint.

Recommended Follow-up: CI Lint Rule

As an additional safeguard, consider adding a lint rule to your CI pipeline that flags any tRPC procedure using publicProcedure that also accesses ctx.session, ctx.user, or any user-scoped data source. This would surface accidental misconfigurations at pull-request time, before they reach a deployed environment.

Example check logic (pseudocode):

For each tRPC router file:
  If procedure is declared with publicProcedure:
    If handler body references ctx.session OR ctx.user:
      Fail with: "publicProcedure must not access user-scoped context — use protectedProcedure"

SOC2-03 Control

This change directly addresses the SOC2-03: Authentication — Route Protection control, which requires that all routes handling authenticated or user-scoped data are protected at the outermost applicable layer, with no reliance on a single enforcement point.