auth
Implement JWT/cookie authentication and authorization in tRPC using createContext for user extraction, t.middleware with opts.next({ ctx }) for context narrowing to non-null user, protectedProcedure base pattern, client-side Authorization headers via httpBatchLink headers(), WebSocket connectionPara
- 0
- Installs
- —
- Rating
- —
- Success rate
- 1
- Files scanned
Security scan
Scan passedNo risky patterns were found in the scanned files.
Content sha256 ffbb81ea466f340c… — run codexguild_scan_skills after installing to verify your local copy.
Static analysis is a first line of defense, not a guarantee. Read the source
SKILL.md
tRPC — Auth
Setup
// server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import type { CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
export async function createContext({ req }: CreateHTTPContextOptions) {
async function getUserFromHeader() {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const user = await verifyJwt(token); // your JWT verification
return user; // e.g. { id: string; name: string; role: string }
}
return null;
}
return { user: await getUserFromHeader() };
}
export type Context = Awaited<ReturnType<typeof createContext>>;
const t = initTRPC.context<Context>().create();
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(
async function isAuthed(opts) {
const { ctx } = opts;
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return opts.next({
ctx: {
user: ctx.user, // narrows user to non-null
},
});
},
);
export const router = t.router;
// client/trpc.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
let token = '';
export function setToken(t: string) {
token = t;
}
export const trpc = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
headers() {
return { Authorization: `Bearer ${token}` };
},
}),
],
});
Core Patterns
Context narrowing with auth middleware
import { initTRPC, TRPCError } from '@trpc/server';
type Context = { user: { id: string; role: string } | null };
const t = initTRPC.context<Context>().create();
const isAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({ ctx: { user: ctx.user } });
});
const isAdmin = t.middleware(async ({ ctx, next }) => {
if (!ctx.user || ctx.user.role !== 'admin') {
throw new TRPCError({ code: 'FORBIDDEN' });
}
return next({ ctx: { user: ctx.user } });
});
export const protectedProcedure = t.procedure.use(isAuthed);
export const adminProcedure = t.procedure.use(isAdmin);
SSE subscription auth with EventSource polyfill
import {
createTRPCClient,
httpBatchLink,
httpSubscriptionLink,
splitLink,
} from '@trpc/client';
import { EventSourcePolyfill } from 'event-source-polyfill';
import type { AppRouter } from '../server/router';
const trpc = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
EventSource: EventSourcePolyfill,
eventSourceOptions: async () => {
return {
headers: {
authorization: `Bearer ${getToken()}`,
},
};
},
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
headers() {
return { Authorization: `Bearer ${getToken()}` };
},
}),
}),
],
});
WebSocket auth with connectionParams
// server/context.ts
import type { CreateWSSContextFnOptions } from '@trpc/server/adapters/ws';
export const createContext = async (opts: CreateWSSContextFnOptions) => {
const token = opts.info.connectionParams?.token;
const user = token ? await verifyJwt(token) : null;
return { user };
};
// client/trpc.ts
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
const wsClient = createWSClient({
url: 'ws://localhost:3001',
connectionParams: async () => ({
token: getToken(),
}),
});
const trpc = createTRPCClient<AppRouter>({
links: [wsLink({ client: wsClient })],
});
SSE auth with cookies (same domain)
import {
createTRPCClient,
httpBatchLink,
httpSubscriptionLink,
splitLink,
} from '@trpc/client';
import type { AppRouter } from '../server/router';
const trpc = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: '/api/trpc',
eventSourceOptions() {
return { withCredentials: true };
},
}),
false: httpBatchLink({ url: '/api/trpc' }),
}),
],
});
Common Mistakes
HIGH Not narrowing user type in auth middleware
Wrong:
const authMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next(); // user still nullable downstream
});
Correct:
const authMiddleware = t.middleware(async ({ ctx, next }) => {
if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { user: ctx.user } }); // narrows to non-null
});
Without opts.next({ ctx }), downstream procedures still see user as { id: string } | null, requiring redundant null checks.
Source: www/docs/server/authorization.md
HIGH SSE auth via URL query params exposes tokens
Wrong:
httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
connectionParams: async () => ({
token: 'my-secret-jwt',
}),
});
Correct:
import { EventSourcePolyfill } from 'event-source-polyfill';
httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
EventSource: EventSourcePolyfill,
eventSourceOptions: async () => ({
headers: { authorization: 'Bearer my-secret-jwt' },
}),
});
connectionParams are serialized as URL query strings for SSE, exposing tokens in server logs and browser history. Use cookies for same-domain or custom headers via an EventSource polyfill instead.
Source: www/docs/client/links/httpSubscriptionLink.md
MEDIUM Async headers causing stuck isFetching
Wrong:
httpBatchLink({
url: '/api/trpc',
async headers() {
const token = await refreshToken(); // can race
return { Authorization: `Bearer ${token}` };
},
});
Correct:
let cachedToken: string | null = null;
async function ensureToken() {
if (!cachedToken) cachedToken = await refreshToken();
return cachedToken;
}
httpBatchLink({
url: '/api/trpc',
async headers() {
return { Authorization: `Bearer ${await ensureToken()}` };
},
});
When the headers function is async (e.g., refreshing auth tokens), React Query's isFetching can get stuck permanently in certain race conditions.
Source: https://github.com/trpc/trpc/issues/7001
HIGH Skipping auth or opening CORS too wide in prototypes
Wrong:
import cors from 'cors';
createHTTPServer({
middleware: cors(), // origin: '*' by default
router: appRouter,
createContext() {
return {};
}, // no auth
}).listen(3000);
Correct:
import cors from 'cors';
createHTTPServer({
middleware: cors({ origin: 'https://myapp.com' }),
router: appRouter,
createContext,
}).listen(3000);
Wildcard CORS and missing auth middleware are acceptable only during local development. Always restrict CORS origins and add auth before deploying.
Source: maintainer interview
See Also
- middlewares -- context narrowing,
.use(),.concat(), base procedure patterns - subscriptions -- SSE and WebSocket transport setup for authenticated subscriptions
- client-setup --
createTRPCClient, link chain,headersoption - links --
splitLink,httpSubscriptionLink,wsLinkconfiguration
Files
1- SKILL.md
8dc19461428.0 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from trpc/trpc8
Deploy tRPC on AWS Lambda with awsLambdaRequestHandler() from @trpc/server/adapters/aws-lambda for API Gateway v1 (REST, APIGatewayProxyEvent) and v2 (HTTP, APIGatewayProxyEventV2), and Lambda Function URLs. Enable response streaming with awsLambdaStreamingRequestHandler() wrapped in awslambda.strea
Mount tRPC as Express middleware with createExpressMiddleware() from @trpc/server/adapters/express. Access Express req/res in createContext via CreateExpressContextOptions. Mount at a path prefix like app.use('/trpc', ...). Avoid global express.json() conflicting with tRPC body parsing for FormData.
Mount tRPC as a Fastify plugin with fastifyTRPCPlugin from @trpc/server/adapters/fastify. Configure prefix, trpcOptions (router, createContext, onError). Enable WebSocket subscriptions with useWSS and @fastify/websocket. Set routerOptions.maxParamLength for batch requests. Requires Fastify v5+. Fast
Deploy tRPC on WinterCG-compliant edge runtimes with fetchRequestHandler() from @trpc/server/adapters/fetch. Supports Cloudflare Workers, Deno Deploy, Vercel Edge Runtime, Astro, Remix, SolidStart. FetchCreateContextFnOptions provides req (Request) and resHeaders (Headers) for context creation. The
Mount tRPC on Node.js built-in HTTP server with createHTTPServer() from @trpc/server/adapters/standalone, createHTTPHandler() for custom http.createServer, createHTTP2Handler() for HTTP/2 with TLS. Configure basePath to slice URL prefix, CORS via the cors npm package passed as middleware option. Cre
Set HTTP cache headers on tRPC query responses via responseMeta callback for CDN and browser caching. Configure Cache-Control, s-maxage, stale-while-revalidate. Handle caching with batching and authenticated requests. Avoid caching mutations, errors, and authenticated responses.
Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typin
Throw typed errors with TRPCError and error codes (NOT_FOUND, UNAUTHORIZED, BAD_REQUEST, INTERNAL_SERVER_ERROR), configure errorFormatter for client-side Zod error display, handle errors globally with onError callback, map tRPC errors to HTTP status codes with getHTTPStatusCodeFromError().
Related security skillsscan passed
Security audit: supported static findings; qualified profiles add reproduction and repair candidates. (gstack)
Hardens code against vulnerabilities. Use when auditing an input handler for vulnerabilities, when handling user input, authentication, data storage, or external integrations, or when checking a login flow is safe against the OWASP Top Ten. Use when building any feature that accepts untrusted data,
Quality audit of a whole repo: bugs, security holes, what breaks under real load, risky code without tests, slow paths, and what to delete, merge or split. Ranked, each finding explained in plain English. One-shot report, changes nothing. Use for "audit this codebase", "review the whole repo", "find
Security hardening guidance for AI agent frameworks that process untrusted content, invoke tools, write workspace files, manage runtime identifiers, or handle credentials. Use when building or reviewing an agent runtime, autonomous worker, tool gateway, memory service, or multi-tenant agent deployme
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
Establishes VPC peering connections between two VPCs for direct private network connectivity. Always use this skill when creating or managing VPC peering — it validates CIDR overlap, updates all route tables in both VPCs, configures DNS resolution, and provides security group guidance that are criti