entropy
entropy15mo ago

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)
36 Replies
entropy
entropy15mo 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.
Lucas
Lucas15mo 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,
}
entropy
entropy15mo 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.
entropy
entropy15mo 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.
koko#1337
koko#133715mo 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
entropy
entropy15mo 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.
koko#1337
koko#133715mo ago
cool, glad I was of help 🙂
siobe
siobe9mo 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
carnegiepilled
carnegiepilled9mo ago
same - any update here? @siobe @koko#1337 @Entropy @CsarChvz been stuck on this for a whole day now
entropy
entropy9mo 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)(.*)'],
}
carnegiepilled
carnegiepilled9mo ago
thanks for getting back to me! @Entropy
carnegiepilled
carnegiepilled9mo ago
okay so i did this
No description
carnegiepilled
carnegiepilled9mo ago
will give it a run
carnegiepilled
carnegiepilled9mo ago
and also just to be clear i'm using getAuth to create the context - does that work with your solution?
No description
carnegiepilled
carnegiepilled9mo ago
would masively apprecoate any help you can give :)
entropy
entropy9mo 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.
carnegiepilled
carnegiepilled9mo 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
carnegiepilled
carnegiepilled9mo ago
yeah still not working btw
No description
entropy
entropy9mo 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?
carnegiepilled
carnegiepilled9mo ago
yeah im signed in already
entropy
entropy9mo ago
and in the application code you can get the session, but just not on your tRPC context?
carnegiepilled
carnegiepilled9mo ago
yep ive run out of ideas is it that hard to migrate to nextauth?
entropy
entropy9mo 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
carnegiepilled
carnegiepilled9mo ago
thanks will take a look is that a working implementation though? the repo is from 6 months ago
entropy
entropy9mo ago
no clue just the first thing I found sorry
carnegiepilled
carnegiepilled9mo ago
do you have a working implementaion of app router + trpc + clerk? if so could i see your code?
entropy
entropy9mo 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
carnegiepilled
carnegiepilled9mo ago
okay! thanks :)
entropy
entropy9mo ago
I'm curious if you did const auth = getAuth(opt.req) and then just logged auth what that returns.
carnegiepilled
carnegiepilled9mo ago
null i already tried them all
carnegiepilled
carnegiepilled9mo ago
No description
entropy
entropy9mo ago
hmmm and sorry to ask again, but if you did auth() in your server components it returns the correct info?
carnegiepilled
carnegiepilled9mo ago
let me try that - thanks for the suggestion!
siobe
siobe9mo ago
GitHub
GitHub - isaackoz/t3-app-router-clerk
Contribute to isaackoz/t3-app-router-clerk development by creating an account on GitHub.
siobe
siobe9mo 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()
CsarChvz
CsarChvz9mo ago
GitHub
GitHub - isaackoz/t3-app-router-clerk
Contribute to isaackoz/t3-app-router-clerk development by creating an account on GitHub.