Authorization
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.tsts
import * as trpcNext from '@trpc/server/adapters/next';import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils';export async function createContext({req,res,}: trpcNext.CreateNextContextOptions) {// 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 fnasync function getUserFromHeader() {if (req.headers.authorization) {const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1],);return user;}return null;}const user = await getUserFromHeader();return {user,};}export type Context = Awaited<ReturnType<typeof createContext>>;
server/context.tsts
import * as trpcNext from '@trpc/server/adapters/next';import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils';export async function createContext({req,res,}: trpcNext.CreateNextContextOptions) {// 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 fnasync function getUserFromHeader() {if (req.headers.authorization) {const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1],);return user;}return null;}const user = await getUserFromHeader();return {user,};}export type Context = Awaited<ReturnType<typeof createContext>>;
Option 1: Authorize using resolver
server/routers/_app.tsts
import { initTRPC, TRPCError } from '@trpc/server';import type { Context } from '../context';export const t = initTRPC.context<Context>().create();const appRouter = t.router({// open for anyonehello: t.procedure.input(z.string().nullish()).query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`),// checked in resolversecret: t.procedure.query((opts) => {if (!opts.ctx.user) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return {secret: 'sauce',};}),});
server/routers/_app.tsts
import { initTRPC, TRPCError } from '@trpc/server';import type { Context } from '../context';export const t = initTRPC.context<Context>().create();const appRouter = t.router({// open for anyonehello: t.procedure.input(z.string().nullish()).query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`),// checked in resolversecret: t.procedure.query((opts) => {if (!opts.ctx.user) {throw new TRPCError({ code: 'UNAUTHORIZED' });}return {secret: 'sauce',};}),});
Option 2: Authorize using middleware
server/routers/_app.tsts
import { initTRPC, TRPCError } from '@trpc/server';export const t = initTRPC.context<Context>().create();// you can reuse this for any procedureexport const protectedProcedure = t.procedure.use(async function isAuthed(opts,) {const { ctx } = opts;// `ctx.user` is nullableif (!ctx.user) {// ^?throw new TRPCError({ code: 'UNAUTHORIZED' });}return opts.next({ctx: {// ✅ user value is known to be non-null nowuser: ctx.user,// ^?},});});t.router({// this is accessible for everyonehello: t.procedure.input(z.string().nullish()).query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`),admin: t.router({// this is accessible only to adminssecret: protectedProcedure.query((opts) => {return {secret: 'sauce',};}),}),});
server/routers/_app.tsts
import { initTRPC, TRPCError } from '@trpc/server';export const t = initTRPC.context<Context>().create();// you can reuse this for any procedureexport const protectedProcedure = t.procedure.use(async function isAuthed(opts,) {const { ctx } = opts;// `ctx.user` is nullableif (!ctx.user) {// ^?throw new TRPCError({ code: 'UNAUTHORIZED' });}return opts.next({ctx: {// ✅ user value is known to be non-null nowuser: ctx.user,// ^?},});});t.router({// this is accessible for everyonehello: t.procedure.input(z.string().nullish()).query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`),admin: t.router({// this is accessible only to adminssecret: protectedProcedure.query((opts) => {return {secret: 'sauce',};}),}),});