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ā€¢16mo 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
BigLungā€¢16mo ago
Which do you recommend for performance, security, and the best UX?
Dylz
Dylzā€¢16mo 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
BigLungā€¢16mo 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
BigLungā€¢16mo ago
BigLung
BigLungā€¢16mo ago
like inside of the auth.ts file that t3 ships with? @ed Adding you to this thread for context
deforestor
deforestorā€¢16mo 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ā€¢16mo 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ā€¢16mo ago
are you using next auth?
deforestor
deforestorā€¢16mo ago
Yeah, I just saw that. We definitely should have it on session Yes
Dylz
Dylzā€¢16mo 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ā€¢16mo ago
next auth is awful at expanding that into the role side of auth
Dylz
Dylzā€¢16mo 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ā€¢16mo ago
We already have the role in the session
Coded_58
Coded_58ā€¢16mo 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
Want results from more Discord servers?
Add your server