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
Before you start
Example repository
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.
// ...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.
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 }
.
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.
.
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.
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.
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
Last updated on