T
Join ServertRPC
❓-help
Issue with trpc fetch when trying to pass Clerk auth to context
I'm currently trying to add Clerk auth to my trpc context, but I just keep getting this error:
This is my code:
Error: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
This is my code:
context.ts
import { NextRequest } from 'next/server'
import { getAuth } from '@clerk/nextjs/server'
import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'
export function createContext(opts: FetchCreateContextFnOptions) {
const auth = getAuth(opts.req as NextRequest)
return {
auth,
headers: opts && Object.fromEntries(opts.req.headers)
}
}
export type Context = Awaited<ReturnType<typeof createContext>>
trpc.ts
import { TRPCError, initTRPC } from '@trpc/server'
import superjson from 'superjson'
import { ZodError } from 'zod'
import { Context } from './context'
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter(opts) {
const { shape, error } = opts
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null
}
}
}
})
const isAuthed = t.middleware(({ next, ctx }) => {
console.log('isAuthed', ctx.auth)
if (!ctx.auth.userId) {
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated' })
}
return next({
ctx: {
auth: ctx.auth
}
})
})
export const router = t.router
export const publicProcedure = t.procedure
export const protectedProcedure = t.procedure.use(isAuthed)
Everything also seems to work fine when I log in and make a call to my protectedProcedure, it's only a problem when I log out.
Kinda seems like you're getting the default 404 html page from next as a response. So maybe my first guess would be that you set up the route handler incorrectly
At least everytime I encountered an error like that it was because I had set up something wrong in the route handler
did you export the methods correctly in the route handler like this?
export {
handler as GET,
handler as POST,
}
Yep I did here's my route handler
api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
import { createContext } from '~/server/api/context'
import { appRouter } from '~/server/api/root'
const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext
})
export { handler as GET, handler as POST }
So after a lot of messing around it seems to caused by something with the Clerk Middleware. I'm still not exactly sure what is the problem or why though.
I implemented this the other week, this is how I added the clerk auth to the context
import { getAuth } from '@clerk/nextjs/server'
import { inferAsyncReturnType } from '@trpc/server'
import { CreateNextContextOptions } from '@trpc/server/adapters/next'
import { prisma } from '~/server/prisma'
/** Context creator for testing purposes */
const createInnerTRPCContext = ({ req }: CreateNextContextOptions) => {
return {
prisma,
auth: getAuth(req),
}
}
/**
* This is the actual context you will use in your router. It will be used to process every request
* that goes through your tRPC endpoint.
*
* @see https://trpc.io/docs/context
*/
export const createTRPCContext = (opts: CreateNextContextOptions) => {
return createInnerTRPCContext(opts)
}
export type TRPCContext = inferAsyncReturnType<typeof createTRPCContext>
and for the middleware:
import { getAuth, withClerkMiddleware } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// Set the paths that don't require the user to be signed in
const publicPaths = ['/sign-in*', '/sign-up*', '/landing'],
isPublic = (path: string) =>
publicPaths.find((x) => path.match(new RegExp(`^${x}$`.replace('*$', '($|/)')))),
authedEndpoints = ['/api/trpc*'],
isAuthedEndpoint = (path: string) =>
authedEndpoints.find((x) => path.match(new RegExp(`^${x}$`.replace('*$', '($|/)'))))
export default withClerkMiddleware((request: NextRequest) => {
if (isPublic(request.nextUrl.pathname)) {
return NextResponse.next()
}
// if the user is not signed in redirect them to the sign in page.
const { userId } = getAuth(request)
// prevent trpc calls if user not authed
if (isAuthedEndpoint(request.nextUrl.pathname) && !userId) {
return NextResponse.json(
{
message: 'UNAUTHORIZED',
},
{ status: 400 }
)
}
if (!userId) {
// redirect the users to sign in
const signInUrl = new URL('/sign-in', request.url)
signInUrl.searchParams.set('redirect_url', request.url)
return NextResponse.redirect(signInUrl)
}
return NextResponse.next()
})
// Stop Middleware running on static files and public folder
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next
* - static (static files)
* - favicon.ico (favicon file)
* - public folder
* - trpc and api endpoints
*/
'/((?!static|.*\\..*|_next|favicon.ico).*)',
'/',
'/(api|trpc)(.*)',
],
}
in my case any TRPC calls would happen post auth, so I added the auth check in the middleware, but feel free to remove that if your case differs
I see thank you for the information! It turns out that Clerk will send everything to the sign in page including api's so I just had to add my api route for trpc to my clerk middleware public routes.
cool, glad I was of help 🙂