Skip to main content
Docs

Integrate Clerk into your Next.js + tRPC app

You will learn the following:

  • Create a Clerk authentication context
  • Use the Clerk context in tRPC queries
  • Access the context data in your backend
  • Create a protected procedure
  • Use your protected procedure

Clerk's Auth object includes important authentication information like the current user's session ID, user ID, and organization ID. It also contains methods to check for the current user's permissions and to retrieve their session token. You can use the Auth object to access the user's authentication information in your tRPC queries.

This guide demonstrates how to create a Clerk authentication context and use it in a tRPC query. It assumes that you have already integrated Clerk into your app by following the quickstart.

Update your providers

When creating your tRPC client, you create a tRPC provider in order to use the tRPC client in your app. Your tRPC provider must be wrapped by Clerk's <ClerkProvider> component in order for tRPC to have access to Clerk's authentication context.

app/layout.tsx
// ...Imports and other code...

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <ClerkProvider>
      <TRPCProvider>
        <html lang="en">
          <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
            <header className="flex justify-end items-center p-4 gap-4 h-16">
              <SignedOut>
                <SignInButton />
                <SignUpButton />
              </SignedOut>
              <SignedIn>
                <Link href="/">Home</Link>
                <UserButton />
              </SignedIn>
            </header>
            {children}
          </body>
        </html>
      </TRPCProvider>
    </ClerkProvider>
  )
}

Create the tRPC context

Create a file that will be used to create the context for every tRPC query sent to the server. The context will use the auth() helper from Clerk to access the user's Auth object.

app/server/context.ts
import { auth } from '@clerk/nextjs/server'

export const createContext = async () => {
  return { auth: await auth() }
}

export type Context = Awaited<ReturnType<typeof createContext>>

Then, in your tRPC server, pass the context to the createContext parameter in the fetchRequestHandler() function. For this guide, the context is named createContext, which is the same as the parameter name, so it's passed as a shorthand property. If you named the context something different, you would need to pass it as a named property, like { createContext: exampleContext }.

app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { appRouter } from '@/app/server/routers/posts'
import { createContext } from '@/app/server/context'

const handler = (req: Request) =>
  fetchRequestHandler({
    endpoint: '/api/trpc',
    req,
    router: appRouter,
    createContext,
  })

export { handler as GET, handler as POST }

Access the context data in your procedures

The tRPC context, or ctx, should now have access to the Clerk Auth object.

In the following example, the ctx is used to access the user's ID and return a greeting message. If the user is not signed in, the greeting will return Hello! You are not signed in..

app/server/routers/index.ts
import { router, publicProcedure } from '../trpc'

export const exampleRouter = router({
  hello: publicProcedure.query(({ ctx }) => {
    const { userId } = ctx.auth

    if (!userId) {
      return {
        greeting: 'Hello! You are not signed in.',
      }
    }

    return {
      greeting: `Hello ${userId}!`,
    }
  }),
})

export type exampleRouter = typeof exampleRouter

Create a protected procedure

In many applications, it's essential to restrict access to certain routes based on user authentication status. This ensures that sensitive data and functionality are only accessible to authorized users. tRPC middleware provides a powerful mechanism for implementing this protection within your application.

In the following example, tRPC middleware is used to access the ctx, which contains the user's authentication information. If the user's ID exists in the authentication context, this means that the user is signed in. If they are not signed in, an UNAUTHORIZED error is thrown.

app/server/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server'
import { Context } from './context'

const t = initTRPC.context<Context>().create()

// Check if the user is signed in
// Otherwise, throw an UNAUTHORIZED code
const isAuthed = t.middleware(({ next, ctx }) => {
  if (!ctx.auth.userId) {
    throw new TRPCError({ code: 'UNAUTHORIZED' })
  }
  return next({
    ctx: {
      auth: ctx.auth,
    },
  })
})

export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)

Use your protected procedure

Once you have created your procedure, you can use it in any router.

In the following example, the protected procedure is used to return a secret message that includes the user's ID. If the user is not signed in, the hello procedure will return an UNAUTHORIZED error, as configured in the step above.

src/server/routers/index.ts
import { router, protectedProcedure } from '../trpc'

export const protectedRouter = router({
  hello: protectedProcedure.query(({ ctx }) => {
    const { userId } = ctx.auth

    return {
      secret: `${userId} is using a protected procedure`,
    }
  }),
})

Feedback

What did you think of this content?

Last updated on