Roles based authentication using Nextauth and next.js

RMAG news

Hello , so if you also wondered around the dark ally of internet searching for your own Auth and roles based solutions but you couldn’t find any or maybe you did it’s just doesn’t work anymore then you are at the right place with the functional code i’ll also give the packages version so it would be easier for you guys.

now let’s first install all the packages you’ll need

npm install next-auth@beta
npm install drizzle-orm zod react-hook-form

now let’s setup the auth providers for nextAuth will create a file in our

lib/auth/index.ts

in this file we are gonna use the credentails provider because by default OAuth doesn’t give us any roles back but we are also gonna see how to use oAuth to assign roles

export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [
Credentials({
name: credentials,
credentials: {
email: {
type: email,
label: Email Address,
placeholder: Email Address,
},
password: {
type: password,
label: Password,
},
},
async authorize(credentials) {
const { email, password } = await signInSchema.parseAsync(credentials);

const user = await db
.select()
.from(users)
.where(eq(users.email, email))
.execute()
.then((res) => res[0]);

if (!user || !user.password) return null;

const isValidPassword = await bcryptjs.compare(password, user.password);

if (!isValidPassword) return null;

return {
id: user.id,
name: user.name,
email: user.email,
};
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,

profile(profile: GoogleProfile) {
return { profile, role: user };
},
})
],
});

Maybe it’s a good time to mention that i’m using postgres and drizzle ORM to save and retrieve this information from DB.

we are using drizzle adapter for it you can installed it using

npm install drizzle-orm @auth/drizzle-adapter
npm install drizzle-kit –save-dev

you can find the drizzle suited schema in here link for the auth to work . now we just have to use this handler in the api routes to make it work.

import { handlers } from @/lib/auth;

export const { GET, POST } = handlers;

now the auth works but there’s no rolese yet. first we’ll modify the drizzle schema and then our nextAuth options.

Remember this can be a simple field as roles in the users table which holds a string value . but I made it this way so i can make as many roles as i want and add permission to it

export const roles = pgTable(roles, {
id: text(id)
.primaryKey()
.$defaultFn(() => createId()),
name: text(name).notNull(),
description: text(description),
});

export const permissions = pgTable(permissions, {
id: text(id)
.primaryKey()
.$defaultFn(() => createId()),
name: text(name).notNull(),
description: text(description),
});

export const rolePermissions = pgTable(role_permissions, {
roleId: text(roleId).references(() => roles.id, { onDelete: cascade }),
permissionId: text(permissionId).references(() => permissions.id, {
onDelete: cascade,
}),
});

export const userRoles = pgTable(user_roles, {
userId: text(userId).references(() => users.id, { onDelete: cascade }),
roleId: text(roleId).references(() => roles.id, { onDelete: cascade }),
});

now we need to modify the NextAuthOption so roles and permission get included in the user session.

first we’ll define the callback functions for getting the roles ourselves .

callbacks: {
async jwt({ token, user }) {
if (user && user.id) {
const { role, permissions } = await getUserRoleAndPermissions(user.id);
token.role = role;
token.permissions = permissions;
}
return token;
},
async session({ session, token }) {
if (token && session.user) {
session.user.id = token.id;
session.user.role = token.role;
session.user.permissions = token.permissions;
}
return session;
},
},

The getRolesandPermission function does exactly as it sounds it uses drizzle to query roles and permission from db . By default this alone won’t work we also need to make some changes in the types.

declare module next-auth {
interface Session {
user: {
id: string;
role: string;
permissions: string[];
} & DefaultSession[user];
}
}

declare module next-auth/jwt {
interface JWT {
id: string;
role: string;
permissions: string[];
}
}

now by accessing the session we can get roles and permission. and by using this you can block the user at page level or by using middleware a whole route group you can protect.

this method can be really useful in a multi tenant sass may be you don’t wanna save your user elsewhere this is a perfect solutions. Thank you for reading this

Please follow and like us:
Pin Share