Implementing User Roles

Hello, I am trying to implement roles of USER, MODERATOR, or ADMIN I find the admin and organization plugins a bit intimidating but more importantly I am not sure if they are the exact use case for my roles here is my current code
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
...
user: {
additionalFields: {
role: {
type: "string",
required: false,
input: false,
},
},
},
databaseHooks: {
user: {
create: {
before: async (user) => {
const ADMIN_EMAILS = process.env.ADMIN_EMAILS?.split(";") || [];

if (ADMIN_EMAILS.includes(user.email)) {
return { data: { ...user, role: UserRole.ADMIN } };
}

const MODERATOR_EMAILS =
process.env.MODERATOR_EMAILS?.split(";") || [];
if (MODERATOR_EMAILS.includes(user.email)) {
return { data: { ...user, role: UserRole.MODERATOR } };
}

return { data: user };
},
},
},
}
});
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
...
user: {
additionalFields: {
role: {
type: "string",
required: false,
input: false,
},
},
},
databaseHooks: {
user: {
create: {
before: async (user) => {
const ADMIN_EMAILS = process.env.ADMIN_EMAILS?.split(";") || [];

if (ADMIN_EMAILS.includes(user.email)) {
return { data: { ...user, role: UserRole.ADMIN } };
}

const MODERATOR_EMAILS =
process.env.MODERATOR_EMAILS?.split(";") || [];
if (MODERATOR_EMAILS.includes(user.email)) {
return { data: { ...user, role: UserRole.MODERATOR } };
}

return { data: user };
},
},
},
}
});
Is this a good approach? I have the default role as USER Or should I be using the admin or organization plugins and adjusting them to fit my above requirements?
2 Replies
KiNFiSH
KiNFiSH2w ago
you can actually do that but you can get more benefit and customization with less bloat - with accessControls - like you can define your ac's and roles like this - `
import { createAccessControl } from "better-auth/plugins/access";

const statements = {
user: ["create", "read", "update", "delete", "ban"],
content: ["create", "read", "update", "delete", "moderate"],
settings: ["read", "update"]
} as const;

// Create the access control instance
const ac = createAccessControl(statements);

export const roles = {
user: ac.newRole({
user: ["read"],
content: ["read", "create"],
settings: ["read"]
}),

moderator: ac.newRole({
user: ["read"],
content: ["read", "create", "update", "delete", "moderate"],
settings: ["read"]
}),

admin: ac.newRole({
user: ["create", "read", "update", "delete", "ban"],
content: ["create", "read", "update", "delete", "moderate"],
settings: ["read", "update"]
})
};

export { ac };
import { createAccessControl } from "better-auth/plugins/access";

const statements = {
user: ["create", "read", "update", "delete", "ban"],
content: ["create", "read", "update", "delete", "moderate"],
settings: ["read", "update"]
} as const;

// Create the access control instance
const ac = createAccessControl(statements);

export const roles = {
user: ac.newRole({
user: ["read"],
content: ["read", "create"],
settings: ["read"]
}),

moderator: ac.newRole({
user: ["read"],
content: ["read", "create", "update", "delete", "moderate"],
settings: ["read"]
}),

admin: ac.newRole({
user: ["create", "read", "update", "delete", "ban"],
content: ["create", "read", "update", "delete", "moderate"],
settings: ["read", "update"]
})
};

export { ac };
and pass it admin plugin here
plugins: [
admin({
ac,
roles,
defaultRole: "user",
adminRoles: ["admin"],
// Optional: List of user IDs that should always have admin access
adminUserIds: process.env.ADMIN_USER_IDS?.split(",") || []
})
],
plugins: [
admin({
ac,
roles,
defaultRole: "user",
adminRoles: ["admin"],
// Optional: List of user IDs that should always have admin access
adminUserIds: process.env.ADMIN_USER_IDS?.split(",") || []
})
],
and now you can check for a permission like this - check on client
const canModerateContent = await client.admin.hasPermission({
permission: {
content: ["moderate"]
}
});
const canModerateContent = await client.admin.hasPermission({
permission: {
content: ["moderate"]
}
});
check on server
const canBanUsers = await auth.api.userHasPermission({
body: {
permission: {
user: ["ban"]
}
}
});
const canBanUsers = await auth.api.userHasPermission({
body: {
permission: {
user: ["ban"]
}
}
});
KHRM
KHRMOP2w ago
Oh this is good stuff thank you for this really helped me better understand the admin plugin in general too Does the list of adminUserIds override the actual role of the user in the database? @KiNFiSH are you able to help, i made a simpler approach
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from "better-auth/plugins/admin/access";

const statements = {
...defaultStatements,
posts: ["create", "read", "update", "delete"],
} as const;

export const ac = createAccessControl(statements);

export const user = ac.newRole({
posts: ["create", "read", "update", "delete"],
});

export const admin = ac.newRole({
...adminAc.statements,
posts: ["read", "update", "delete"],
});
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from "better-auth/plugins/admin/access";

const statements = {
...defaultStatements,
posts: ["create", "read", "update", "delete"],
} as const;

export const ac = createAccessControl(statements);

export const user = ac.newRole({
posts: ["create", "read", "update", "delete"],
});

export const admin = ac.newRole({
...adminAc.statements,
posts: ["read", "update", "delete"],
});
but when i log in with a user who has user.session.role === 'user' this fails
const canCreatePosts = await auth.api.userHasPermission({
body: {
permission: {
posts: ["create"],
},
},
});
console.log({ canCreatePosts });
//{ canCreatePosts: { error: null, success: false } }
const canCreatePosts = await auth.api.userHasPermission({
body: {
permission: {
posts: ["create"],
},
},
});
console.log({ canCreatePosts });
//{ canCreatePosts: { error: null, success: false } }
plugins: [
adminPlugin({
defaultRole: "user",
adminRoles: ["admin"],
ac,
roles: {
user,
admin,
},
}),
],
plugins: [
adminPlugin({
defaultRole: "user",
adminRoles: ["admin"],
ac,
roles: {
user,
admin,
},
}),
],
bump I believe the reason is I did not pass headers in the function So if I want to check if the currently logged in user has permission I can do either one of the following right? pass the headers OR Pass the userId if I called getSession earlier in the code?

Did you find this page helpful?