Custom protected routes?

Hey guys I'm wondering about the best way to achieve something. I added a custom property to my User table which is ROLE Role can be USER, ADMIN, or SUPER_ADMIN I want to gate a route /admin and all of its potential future subroutes behind a check to ensure someones role is ADMIN.
const Admin = () => {
const { data: sessionData } = useSession();

const { data: isAdmin, isLoading: isAdminLoading } =
api.user.isAdmin.useQuery(
{
userId: sessionData?.user?.id as string,
},
{
enabled: sessionData?.user !== undefined,
}
);

if (!isAdmin)
return (
<div>
<H1>You are not authorized to view this page</H1>
</div>
);

return <div>Admin</div>;
};

export default Admin;
const Admin = () => {
const { data: sessionData } = useSession();

const { data: isAdmin, isLoading: isAdminLoading } =
api.user.isAdmin.useQuery(
{
userId: sessionData?.user?.id as string,
},
{
enabled: sessionData?.user !== undefined,
}
);

if (!isAdmin)
return (
<div>
<H1>You are not authorized to view this page</H1>
</div>
);

return <div>Admin</div>;
};

export default Admin;
isAdmin: publicProcedure
.input(
z.object({
userId: z.string(),
})
)
.query(async ({ ctx, input }): Promise<boolean> => {
return await ctx.prisma.user
.findUnique({
where: {
id: input.userId,
},
})
.then((user) => {
if (user?.role === "ADMIN") return true;
return false;
});
}),
isAdmin: publicProcedure
.input(
z.object({
userId: z.string(),
})
)
.query(async ({ ctx, input }): Promise<boolean> => {
return await ctx.prisma.user
.findUnique({
where: {
id: input.userId,
},
})
.then((user) => {
if (user?.role === "ADMIN") return true;
return false;
});
}),
I was adding this to each page but this seems obtuse / incorrect. Is there a better way to gate entire routes behind custom checks than what I am doing here?
15 Replies
Neto
Neto•2y ago
you have three options client side check, middlewares and gssp - 1st -> block the whole page until you have check validation of the role - 2nd -> on middlewares, check the route/role - 3rd -> on the page creation, check if user is admin, and continue or redirect from there
BigLung
BigLungOP•2y ago
Which do you recommend for performance, security, and the best UX?
Dylz
Dylz•2y ago
I'd guess number 2, but ill defer to @nyx (Rustular DevRel) Checking client side is much slower, especially if you're using db sessions since it goes from server --> client --> server --> client
BigLung
BigLungOP•2y ago
Oof good point Would it be crazy to extend the session type and include my user.ROLE property directly on the session? So that I could access it from useSession? šŸ¤” @dylz1 Im pretty stupid, what does he mean by "on middlewars, check route / role?"
BigLung
BigLungOP•2y ago
BigLung
BigLungOP•2y ago
like inside of the auth.ts file that t3 ships with? @ed Adding you to this thread for context
deforestor
deforestor•2y ago
I've never done it with middleware, it's worth a try, but why wonder why would it be more performant? But it's definitely better than having to do getServerSideProps on every page that should be guarded
Dylz
Dylz•2y ago
@biglung not a bad idea to include the role in session, its possible, but a little bit tedious, i know someone posted a guide somewhere in here
Neto
Neto•2y ago
are you using next auth?
deforestor
deforestor•2y ago
Yeah, I just saw that. We definitely should have it on session Yes
Dylz
Dylz•2y ago
in your root folder, 'src'. Make a middleware.ts and put something like this in it: export { default } from 'next-auth/middleware' export const config = { matcher: ['/TESTEST'] }
Neto
Neto•2y ago
next auth is awful at expanding that into the role side of auth
Dylz
Dylz•2y ago
yeah i ended up just not including it in the session since it was such a pain, seemed like every time i thought i got it working there was another edge case that was broken althought for the middleware route, you would need the role to be in the session
deforestor
deforestor•2y ago
We already have the role in the session
Coded_58
Coded_58•2y ago
middleware worked in my case compare to client side checks. Along with the initial question, how do you route a user to their specific role route. lets say the admin should be routed to www.blahblah.com/admin, while super-admin be routed to .com/super-admin and same for user. what i did was redirect my login function to a special route (page component) that does the check on the server before routing the logged in user their role returned by the session... I don't know if there's a more elegant way of going about it

Did you find this page helpful?