Thimo_o
Thimo_o17mo ago

How do I setup App router + TRPC + Lucia Auth? (protected routes)

I'm trying to setup tRPC with Lucia in app router but I struggle to make protected routes work. I made a trpc context where I put lucia session and a getDefaultSession function to get the session
export const getDefaultSession = cache((req: NextRequest) => {
const authRequest = auth.handleRequest({
request: req,
cookies,
})
return authRequest.validate()
})

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
}
}

export const createTRPCContext = async (req: NextRequest) => {
const session = await getDefaultSession(req)

return createInnerTRPCContext({
session,
})
}
export const getDefaultSession = cache((req: NextRequest) => {
const authRequest = auth.handleRequest({
request: req,
cookies,
})
return authRequest.validate()
})

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
}
}

export const createTRPCContext = async (req: NextRequest) => {
const session = await getDefaultSession(req)

return createInnerTRPCContext({
session,
})
}
I wrote a isAuth function to make the protected procedure
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session?.user) {
throw new TRPCError({ code: "UNAUTHORIZED" })
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
})
})

export const protectedProcedure = t.procedure.use(enforceUserIsAuthed)
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.session || !ctx.session?.user) {
throw new TRPCError({ code: "UNAUTHORIZED" })
}
return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
})
})

export const protectedProcedure = t.procedure.use(enforceUserIsAuthed)
and added it in my route.ts handler
const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createTRPCContext(req),
})

export { handler as GET, handler as POST }
const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createTRPCContext(req),
})

export { handler as GET, handler as POST }
But the session is not passed into ctx resulting in always getting the unauthorized error. Does someone know what I did wrong to make this work?
6 Replies
.traevelliath
.traevelliath17mo ago
Jack Herrington
YouTube
tRPC + NextJS App Router = Simple Typesafe APIs
Upcoming course: pronextjs.dev 👉 Code : https://github.com/jherr/trpc-on-the-app-router 👉 Don't forget to subscribe to this channel for more updates: https://bit.ly/2E7drfJ 👉 Discord server signup: https://discord.gg/ddMZFtTDa5 👉 VS Code theme and font? Night Wolf [black] and Operator Mono 👉 Terminal Theme and font? oh-my-posh with powerlevel1...
Thimo_o
Thimo_oOP17mo ago
This is the video I initially followed, but it doesn't provide protected procedures. My solution was to change the serverClient into:
export const serverClient = ({ session }: { session: Session | null }) => {
return appRouter.createCaller({ session })
}
export const serverClient = ({ session }: { session: Session | null }) => {
return appRouter.createCaller({ session })
}
.traevelliath
.traevelliath17mo ago
Why? Look how protected procedures were implemented by Theo, for example, in his Chirp guide. And do the same.
Thimo_o
Thimo_oOP17mo ago
It was a bit more difficult to get right on the rsc side, but I made it work in almost a similar way, I only miss the useQuery part now
siobe
siobe14mo ago
"But the session is not passed into ctx resulting in always getting the unauthorized error. Does someone know what I did wrong to make this work?" I'm running into the exact same issue trying to get Clerk working with app router (Theo's Chirp video showed it working in pages router, not in app router). How did you manage to get the ctx to pass the request with the auth info in this line: const session = await getDefaultSession(req) With Clerk I'm trying to get the auth session with const auth = getAuth(req) and it always returns null
Endgame1013
Endgame101314mo ago
What I’ve used in a couple projects:
import * as context from 'next/headers';

export const isAuthed = t.middleware(async (opts) => {
const { req } = opts.ctx;
const authRequest = auth.handleRequest(req.method, context);
const session = await authRequest.validate();
if (!session) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authorized!' });
return opts.next({
ctx: {
// 👇 infer session, auth, and authRequest as non-nullable
session,
auth,
authRequest,
},
});
});

export const protectedProcedure = t.procedure.use(loggerMiddleware).use(isAuthed);
import * as context from 'next/headers';

export const isAuthed = t.middleware(async (opts) => {
const { req } = opts.ctx;
const authRequest = auth.handleRequest(req.method, context);
const session = await authRequest.validate();
if (!session) throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authorized!' });
return opts.next({
ctx: {
// 👇 infer session, auth, and authRequest as non-nullable
session,
auth,
authRequest,
},
});
});

export const protectedProcedure = t.procedure.use(loggerMiddleware).use(isAuthed);

Did you find this page helpful?