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: 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>>
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)
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)
E
entropy340d ago
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.
L
Lucas340d ago
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,
}
export {
handler as GET,
handler as POST,
}
E
entropy340d ago
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 }
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.
E
entropy340d ago
here's a link to a repo with the same issue as well: https://github.com/Entropy-10/clerk-trpc-issue
GitHub
GitHub - Entropy-10/clerk-trpc-issue
Contribute to Entropy-10/clerk-trpc-issue development by creating an account on GitHub.
K
koko#1337340d ago
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>
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)(.*)',
],
}
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
E
entropy339d ago
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.
K
koko#1337338d ago
cool, glad I was of help 🙂
S
siobe173d ago
i'm new here so sorry if i'm asking in the wrong place, but did this code snippet work for you when using the app router or pages router? been trying to implement Clerk authentication with t3 using the app router and running into issues with Clerk's getAuth() and auth() methods both returning null for auth specifically in this line: auth: getAuth(req), in the createInnerTRPCContext method
C
carnegiepilled165d ago
same - any update here? @siobe @koko#1337 @Entropy @CsarChvz been stuck on this for a whole day now
E
entropy165d ago
you just need to add your api to your public routes on the Clerk middleware so:
export default authMiddleware({
publicRoutes: ["<YOUR API ENDPOINT>"]
})
export default authMiddleware({
publicRoutes: ["<YOUR API ENDPOINT>"]
})
Though if you are following the Clerk docs and use the matcher they have it should prevent your middleware from running on /trpc or /api endpoints
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}
C
carnegiepilled165d ago
thanks for getting back to me! @Entropy
C
carnegiepilled165d ago
okay so i did this
No description
C
carnegiepilled165d ago
will give it a run
C
carnegiepilled165d ago
and also just to be clear i'm using getAuth to create the context - does that work with your solution?
No description
C
carnegiepilled165d ago
would masively apprecoate any help you can give :)
E
entropy165d ago
They actually have a guide that can help you setup tRPC with Clerk here that I'd recommend following. https://clerk.com/docs/references/nextjs/trpc
Integrate Clerk into your Next.js app with tRPC | Clerk
Learn how to integrate Clerk into your Next.js application using tRPC. tRPC can be used with Clerk, but requires a few tweaks from a traditional Clerk + Next.js setup.
C
carnegiepilled165d ago
yeah i already setup that whole thing i've been like banging my head against the wall on this for >1d its so frustrating bc those docs don't work + are old
C
carnegiepilled165d ago
yeah still not working btw
No description
E
entropy165d ago
also it's redundant to have the / and /api/(.*) routes in your publicRoutes since the matcher already prevents the middleware from running on those routes. Is that after you go through the sign in flow?
C
carnegiepilled165d ago
yeah im signed in already
E
entropy165d ago
and in the application code you can get the session, but just not on your tRPC context?
C
carnegiepilled165d ago
yep ive run out of ideas is it that hard to migrate to nextauth?
E
entropy165d ago
Uh I mean really depends on what you need but I would say compared to Clerk that next-auth is a bit harder to setup. I found this github really quickly if you want to take a peak at their implementation. Otherwise I would wait for someone more knowledgeable to help out. https://github.com/solaldunckel/next-13-app-router-with-trpc
GitHub
GitHub - solaldunckel/next-13-app-router-with-trpc: Next 13 app dir...
Next 13 app dir with tRPC, Kysely, Planetscale and Turborepo - GitHub - solaldunckel/next-13-app-router-with-trpc: Next 13 app dir with tRPC, Kysely, Planetscale and Turborepo
C
carnegiepilled165d ago
thanks will take a look is that a working implementation though? the repo is from 6 months ago
E
entropy165d ago
no clue just the first thing I found sorry
C
carnegiepilled165d ago
do you have a working implementaion of app router + trpc + clerk? if so could i see your code?
E
entropy165d ago
Let me check to see really quick this was from a while ago when I was testing for a tech stack, but decided to not go with tRPC or Clerk
C
carnegiepilled165d ago
okay! thanks :)
E
entropy165d ago
I'm curious if you did const auth = getAuth(opt.req) and then just logged auth what that returns.
C
carnegiepilled165d ago
null i already tried them all
C
carnegiepilled165d ago
No description
E
entropy165d ago
hmmm and sorry to ask again, but if you did auth() in your server components it returns the correct info?
C
carnegiepilled165d ago
let me try that - thanks for the suggestion!
S
siobe165d ago
GitHub
GitHub - isaackoz/t3-app-router-clerk
Contribute to isaackoz/t3-app-router-clerk development by creating an account on GitHub.
S
siobe165d ago
here's a working repo but uses cookies from opts to verify the session rather than the getAuth method i couldn't get it to work when i passed opt.req to auth() or to getAuth()
C
CsarChvz163d ago
GitHub
GitHub - isaackoz/t3-app-router-clerk
Contribute to isaackoz/t3-app-router-clerk development by creating an account on GitHub.
More Posts
BullMQ + TRPCCurious how to configure BullMQ with TRPC correctly. Currently I have the queue, queueevents, and thTrpc refetches all of the queries when i run a mutationIm running node 16.15.0 with Pnpm When i run a mutation for some reason all of the queries get refettRPC Client on NodeJS server keeps complaining that no fetcher has been configuredHey, I want to create a tRPC setup where I have one server (works fine) and then a client which is cBest Practice to Fetch a Query OnDemandWhat's the best practice to fetch a query on demand? I don't have the context for the query's inputOutput Response ShapeI'm wondering, is the output response shape locked in, or can we modify it in any way? For example: Need help```js import {initTRPC} from '@trpc/server'; import * as trpcNext from '@trpc/server/adapters/next';useQuery hook modify data on fetch.Hello is it possible to modify the data that is fetched with useQuery hook in tRPC basically im storAccepting a DecoratedProcedure with inputs and outputs that extend some given typesIs there any way to accept a DecoratedProcedure that extends { mutate: Resolver<TProcedure> } where useEffect and useMutation error about conditional rendering of hooksI am using t3 stack with tRPC, and I am trying to mark all emails as seen when the page loads by usiGuide to create an adapterIs there a guide on the docs that explains the basics to create an adapter?Does tRPC websocket client supports wss protocol?After changing the websocket client url from ws to wss, it fails to connect. Tested out the connectiGet the type of context after middlewareHow can I get the type of the context of `adminProcedure` from `export const adminProcedure = public[How To] Properly use trpc UseQuery based on currently selected itemI have a component with a video element and a flatlist. I want to utilize a trpc query to get the viData from useQuery is inferred as data: {} | undefinedMy server query returns data from database via ElectroDB. TypeScript can statically infer all properCannot access 't' before initializationHi, I'm migrating my app to a mono repo but I just can't get past this error for some reason `CannottRPC Middleware consuming memory limit on VercelHi all, I'm running into a weird error where my tRPC middleware to enforce that a user is authed, isUsing react-query parameters in tRPC for useQueryHello, the useQuery from react-query can take parameters such cacheTime, staleTime, refetchOnWindowFtRPC type error on turborepo```Types of property 'query' are incompatible. Type 'inferHandlerFn<{}>' is not assignable to tyStack for expo?Can someone recommend a stack for an expo project? I'm considering trpc + fastify + fly.io, but havImplementing a "Prisma model converter middleware"Bit of a fancy name, but if you've ever worked with Symfonys param converters (especially the Doctri