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.ts

  1. import * as trpc from '@trpc/server';
  2. import { inferAsyncReturnType } from '@trpc/server';
  3. import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils';
  4. export async function createContext({
  5. req,
  6. res,
  7. }: trpcNext.CreateNextContextOptions) {
  8. // Create your context based on the request object
  9. // Will be available as `ctx` in all your resolvers
  10. // This is just an example of something you'd might want to do in your ctx fn
  11. async function getUserFromHeader() {
  12. if (req.headers.authorization) {
  13. const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1])
  14. return user;
  15. }
  16. return null;
  17. }
  18. const user = await getUserFromHeader();
  19. return {
  20. user,
  21. };
  22. }
  23. type Context = inferAsyncReturnType<typeof createContext>;
  24. // [..] Define API handler and app router

Option 1: Authorize using resolver

server/routers/_app.ts

  1. import * as trpc from '@trpc/server';
  2. import { TRPCError } from '@trpc/server';
  3. import { createRouter } from '../createRouter';
  4. export const appRouter = createRouter()
  5. // open for anyone
  6. .query('hello', {
  7. input: z.string().nullish(),
  8. resolve: ({ input, ctx }) => {
  9. return `hello ${input ?? ctx.user?.name ?? 'world'}`;
  10. },
  11. })
  12. // checked in resolver
  13. .query('secret', {
  14. resolve: ({ ctx }) => {
  15. if (!ctx.user) {
  16. throw new TRPCError({ code: 'UNAUTHORIZED' });
  17. }
  18. return {
  19. secret: 'sauce',
  20. };
  21. },
  22. });

Option 2: Authorize using middleware

server/routers/_app.ts

  1. import * as trpc from '@trpc/server';
  2. import { TRPCError } from '@trpc/server';
  3. import { createRouter } from '../createRouter';
  4. export const appRouter = createRouter()
  5. // this is accessible for everyone
  6. .query('hello', {
  7. input: z.string().nullish(),
  8. resolve: ({ input, ctx }) => {
  9. return `hello ${input ?? ctx.user?.name ?? 'world'}`;
  10. },
  11. })
  12. .merge(
  13. 'admin.',
  14. createRouter()
  15. // this protects all procedures defined next in this router
  16. .middleware(async ({ ctx, next }) => {
  17. if (!ctx.user?.isAdmin) {
  18. throw new TRPCError({ code: 'UNAUTHORIZED' });
  19. }
  20. return next()
  21. })
  22. .query('secret', {
  23. resolve: ({ ctx }) => {
  24. return {
  25. secret: 'sauce',
  26. }
  27. },
  28. }),
  29. );

This middleware can be re-used for multiple sub-routers by creating a protected router helper.