Authorization
On this page
The createContext function is called for each incoming request, so here you can add contextual information about the calling user from the request object.
Create context from request headers
server/context.ts
import type { type CreateHTTPContextOptions = {
req: IncomingMessage;
res: ServerResponse<IncomingMessage>;
info: TRPCRequestInfo;
}
CreateHTTPContextOptions } from '@trpc/server/adapters/standalone';
import { function decodeAndVerifyJwtToken(token: string): Promise<{
name: string;
}>
decodeAndVerifyJwtToken } from './utils';
export async function function createContext({ req, res }: CreateHTTPContextOptions): Promise<{
user: {
name: string;
} | null;
}>
createContext({ req: IncomingMessagereq, res: ServerResponse<IncomingMessage>res }: type CreateHTTPContextOptions = {
req: IncomingMessage;
res: ServerResponse<IncomingMessage>;
info: TRPCRequestInfo;
}
CreateHTTPContextOptions) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you might want to do in your ctx fn
async function function (local function) getUserFromHeader(): Promise<{
name: string;
} | null>
getUserFromHeader() {
if (req: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.authorization?: string | undefinedauthorization) {
const const user: {
name: string;
}
user = await function decodeAndVerifyJwtToken(token: string): Promise<{
name: string;
}>
decodeAndVerifyJwtToken(
req: IncomingMessagereq.IncomingMessage.headers: IncomingHttpHeadersheaders.IncomingHttpHeaders.authorization?: stringauthorization.String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)split(' ')[1],
);
return const user: {
name: string;
}
user;
}
return null;
}
const const user: {
name: string;
} | null
user = await function (local function) getUserFromHeader(): Promise<{
name: string;
} | null>
getUserFromHeader();
return {
user: {
name: string;
} | null
user,
};
}
export type type Context = {
user: {
name: string;
} | null;
}
Context = type Awaited<T> = T extends null | undefined ? T : T extends object & {
then(onfulfilled: infer F, ...args: infer _): any;
} ? F extends (value: infer V, ...args: infer _) => any ? Awaited<V> : never : T
Awaited<type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : anyReturnType<typeof function createContext({ req, res }: CreateHTTPContextOptions): Promise<{
user: {
name: string;
} | null;
}>
createContext>>;Option 1: Authorize using resolver
server/routers/_app.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
import { import zz } from 'zod';
type type Context = {
user: {
name: string;
} | null;
}
Context = { user: {
name: string;
} | null
user: { name: stringname: string } | null };
export const const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<type Context = {
user: {
name: string;
} | null;
}
Context>().TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
const const appRouter: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
appRouter = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<...>
router({
// open for anyone
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
hello: const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodOptional<z.ZodNullable<z.ZodString>>>(schema: z.ZodOptional<z.ZodNullable<z.ZodString>>): ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.nullish(): z.ZodOptional<z.ZodNullable<z.ZodString>>nullish())
.ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, string | null | undefined, UnsetMarker, string>): QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts) => `hello ${opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.input: string | null | undefinedinput ?? opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.ctx: {
user: {
name: string;
} | null;
}
ctx.user: {
name: string;
} | null
user?.name: string | undefinedname ?? 'world'}`),
// checked in resolver
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
secret: const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
secret: string;
}>(resolver: ProcedureResolver<Context, object, object, UnsetMarker, UnsetMarker, {
secret: string;
}>): QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<Context, object, object, UnsetMarker>opts) => {
if (!opts: ProcedureResolverOptions<Context, object, object, UnsetMarker>opts.ProcedureResolverOptions<Context, object, object, UnsetMarker>.ctx: {
user: {
name: string;
} | null;
}
ctx.user: {
name: string;
} | null
user) {
throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return {
secret: stringsecret: 'sauce',
};
}),
});Option 2: Authorize using middleware
server/routers/_app.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
import { import zz } from 'zod';
type type Context = {
user: {
name: string;
} | null;
}
Context = { user: {
name: string;
} | null
user: { name: stringname: string } | null };
export const const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<type Context = {
user: {
name: string;
} | null;
}
Context>().TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
// you can reuse this for any procedure
export const const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
name: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: {
name: string;
};
}>(fn: MiddlewareBuilder<{
user: {
name: string;
} | null;
}, object, {
user: {
name: string;
};
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
user: {
name: string;
};
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: {
name: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(
async function function (local function) isAuthed(opts: {
ctx: {
user: {
name: string;
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}): Promise<...>
isAuthed(opts: {
ctx: {
user: {
name: string;
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) {
const { const ctx: {
user: {
name: string;
} | null;
}
ctx } = opts: {
ctx: {
user: {
name: string;
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts;
// `ctx.user` is nullable
if (!const ctx: {
user: {
name: string;
} | null;
}
ctx.user) { throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return opts: {
ctx: {
user: {
name: string;
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
user: {
name: string;
};
}>(opts: {
ctx?: {
user: {
name: string;
};
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
user: {
name: string;
};
}>> (+2 overloads)
next({
ctx?: {
user: {
name: string;
};
} | undefined
ctx: {
// ✅ user value is known to be non-null now
user: const ctx: {
user: {
name: string;
} | null;
}
ctx.user: {
name: string;
}
user, },
});
},
);
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}>(_: {
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>;
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>;
}) => BuiltRouter<...>
router({
// this is accessible for everyone
hello: QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
hello: const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodOptional<z.ZodNullable<z.ZodString>>>(schema: z.ZodOptional<z.ZodNullable<z.ZodString>>): ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>input(import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string().ZodType<any, any, $ZodStringInternals<string>>.nullish(): z.ZodOptional<z.ZodNullable<z.ZodString>>nullish())
.ProcedureBuilder<Context, object, object, string | null | undefined, string | null | undefined, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, string | null | undefined, UnsetMarker, string>): QueryProcedure<{
input: string | null | undefined;
output: string;
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts) => `hello ${opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.input: string | null | undefinedinput ?? opts: ProcedureResolverOptions<Context, object, object, string | null | undefined>opts.ProcedureResolverOptions<Context, object, object, string | null | undefined>.ctx: {
user: {
name: string;
} | null;
}
ctx.user: {
name: string;
} | null
user?.name: string | undefinedname ?? 'world'}`),
admin: BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
admin: const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder
<{
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>(_: {
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}) => BuiltRouter<{
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}, DecorateCreateRouterOptions<{
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>;
}>>
router({
// this is accessible only to admins
secret: QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
secret: const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
name: string;
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure.ProcedureBuilder<Context, object, { user: { name: string; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
secret: string;
}>(resolver: ProcedureResolver<Context, object, {
user: {
name: string;
};
}, UnsetMarker, UnsetMarker, {
secret: string;
}>): QueryProcedure<{
input: void;
output: {
secret: string;
};
meta: object;
errorShape: DefaultErrorShape;
}>
query((opts: ProcedureResolverOptions<Context, object, {
user: {
name: string;
};
}, UnsetMarker>
opts) => {
return {
secret: stringsecret: 'sauce',
};
}),
}),
});Building on a protected procedure
Since a procedure's middlewares run in order, you can add more checks on top of protectedProcedure, where ctx.user is already non-null:
server/trpc.ts
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';
type type Context = {
user: {
name: string;
role: "admin" | "user";
} | null;
}
Context = { user: {
name: string;
role: "admin" | "user";
} | null
user: { name: stringname: string; role: "admin" | "user"role: 'admin' | 'user' } | null };
export const const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t = const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<type Context = {
user: {
name: string;
role: "admin" | "user";
} | null;
}
Context>().TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
create();
export const const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure = const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
ctx: Context;
meta: object;
errorShape: DefaultErrorShape;
transformer: false;
}>
t.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: {
name: string;
role: "admin" | "user";
};
}>(fn: MiddlewareBuilder<{
user: {
name: string;
role: "admin" | "user";
} | null;
}, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(async (opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
if (!opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
}
ctx.user: {
name: string;
role: "admin" | "user";
} | null
user) {
throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
}
return opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: <{
user: {
name: string;
role: "admin" | "user";
};
}>(opts: {
ctx?: {
user: {
name: string;
role: "admin" | "user";
};
} | undefined;
input?: unknown;
}) => Promise<MiddlewareResult<{
user: {
name: string;
role: "admin" | "user";
};
}>> (+2 overloads)
next({ ctx?: {
user: {
name: string;
role: "admin" | "user";
};
} | undefined
ctx: { user: {
name: string;
role: "admin" | "user";
}
user: opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<object>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.ctx: {
user: {
name: string;
role: "admin" | "user";
} | null;
}
ctx.user: {
name: string;
role: "admin" | "user";
}
user } });
});
export const const adminProcedure: ProcedureBuilder<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure = const protectedProcedure: ProcedureBuilder<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure.ProcedureBuilder<Context, object, { user: { name: string; role: "admin" | "user"; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
user: {
name: string;
role: "admin" | "user";
};
}>(fn: MiddlewareBuilder<{
user: {
name: string;
role: "admin" | "user";
};
}, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker> | MiddlewareFunction<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker>): ProcedureBuilder<Context, object, {
user: {
name: string;
role: "admin" | "user";
};
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use(async (opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
};
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: {
name: string;
role: "admin" | "user";
};
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts) => {
if (opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
};
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: {
name: string;
role: "admin" | "user";
};
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.ctx: {
user: {
name: string;
role: "admin" | "user";
};
}
ctx.user.role: "admin" | "user"role !== 'admin') { throw new new TRPCError(opts: {
message?: string;
code: TRPC_ERROR_CODE_KEY;
cause?: unknown;
}): TRPCError
TRPCError({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'FORBIDDEN' });
}
return opts: {
ctx: {
user: {
name: string;
role: "admin" | "user";
};
};
type: ProcedureType;
path: string;
input: UnsetMarker;
getRawInput: GetRawInputFn;
meta: object | undefined;
signal: AbortSignal | undefined;
batchIndex: number;
next: {
(): Promise<MiddlewareResult<{
user: {
name: string;
role: "admin" | "user";
};
}>>;
<$ContextOverride>(opts: {
ctx?: $ContextOverride | undefined;
input?: unknown;
}): Promise<MiddlewareResult<$ContextOverride>>;
(opts: {
getRawInput: GetRawInputFn;
}): Promise<...>;
};
}
opts.next: () => Promise<MiddlewareResult<{
user: {
name: string;
role: "admin" | "user";
};
}>> (+2 overloads)
next();
});Throw UNAUTHORIZED (401) when the caller isn't signed in, and FORBIDDEN (403) when they are, but aren't allowed to do this.
Sending credentials from the client
- Cookies are sent with every request when the client and server are on the same origin. For another origin, see CORS & Cookies.
- Headers like
Authorizationcan be set on any HTTP link with theheadersoption. - Subscriptions over SSE can't send custom headers with the browser's
EventSource. SeehttpSubscriptionLinkfor the options. - WebSockets send connection params when the connection opens, which
createContextreads fromopts.info.connectionParams.
Common mistakes
- Calling
opts.next()without the narrowedctx. Procedures after the middleware still seectx.useras nullable. Passopts.next({ ctx: { user: opts.ctx.user } }), as in Option 2. - Throwing in
createContextfor a missing or invalid token. That fails every procedure in the request, public ones too. Returnuser: nullinstead, and let a middleware reject the procedures that need a user. - Putting tokens in
httpSubscriptionLink'sconnectionParams. They are sent in the URL, where servers and proxies may log them. Prefer cookies, or headers through anEventSourceponyfill.