tRPC Procedure Factory Configuration for Default Access Control
0 reputation · 30 Jul 2022, 05:43 UTC
0 reputation · 30 Jul 2022, 05:43 UTC
tRPC relies on a composition pattern where developers define procedure factories, such as publicProcedure and protectedProcedure, to manage access levels. By default, any procedure created from the base factory is accessible to the public unless wrapped in middleware that validates the session context.
In large-scale routers with numerous nested endpoints, relying on the manual selection of a protected factory increases the risk of accidentally exposing sensitive server-side functions. There is currently no native configuration to invert this logic to a "deny-by-default" stance at the router level.
What is the recommended architectural approach to ensure all procedures are protected unless explicitly marked as public? Can a global middleware be configured to enforce authentication across an entire router without manually redefining every procedure factory?
Create a base procedure factory that applies authentication middleware to every procedure, then expose a second factory that skips the middleware for the few endpoints that should be public.
const t = initTRPC.create();ctx.user (or equivalent) and throws new TRPCError({ code: 'UNAUTHORIZED' }) when missing.const protectedProcedure = t.procedure.use(authMiddleware);const publicProcedure = t.procedure;protectedProcedure for private endpoints and publicProcedure for any that must be openly accessible.export const appRouter = t.router({ private: protectedProcedure.query(...), public: publicProcedure.query(...) });The procedure.use call registers middleware only on the factory it is invoked on. All procedures derived from protectedProcedure inherit the auth check, while those derived from publicProcedure do not, giving you a deny‑by‑default posture without touching each endpoint individually.
If you are using a tRPC version prior to v10, the initTRPC.create API differs; please confirm your version so the exact initialization step can be adjusted.
Use comments to ask for clarification. Post a solution as an answer.
31,775 reputation · 30 Jul 2022, 10:35 UTC
Beyond the factory split already described, two practical points are worth adding.
First, invert the naming so the safe choice is the lazy one: export your authenticated factory as the plain procedure and force developers to type publicProcedure deliberately. Accidental exposure then requires an explicit opt-in rather than an opt-out.
Second, conventions drift in large codebases, so back the pattern with a CI check. A small script can recursively walk appRouter._def.record and assert that every resolver outside an allowlist carries the auth middleware's meta. This catches the "forgotten endpoint" case that code review misses.
One caveat: middleware runs per request, so if createContext never populates the session, a truthiness check on the wrong field can fail open. Verify with a test asserting an unauthenticated call to a default-factory procedure throws UNAUTHORIZED. Exact APIs differ between tRPC v10 and v11 — confirm against your installed version.