RBAC Permissions

Configure role-based access control for admin panel features and actions.

MakerKit's admin RBAC system controls what each admin role can do. By default, a single admin role has full permissions. You can add custom roles (support, moderator) with restricted access by editing packages/rbac/src/admin-rbac.config.ts. Permissions are enforced server-side via withAdminPermission() middleware and client-side via the useAdminPermissions hook.

Use singular resource names (user, session) not plural. This matches Better Auth's internal permission model.

Default Configuration

Out of the box, MakerKit provides a single admin role with full permissions:

import { defineAdminRBACConfig } from './admin/factory';
export default defineAdminRBACConfig({
// Default: admin role with all permissions
});

Resources and Actions

Built-in Resources

These resources are controlled by Better Auth and must use exact names:

ResourceActionsDescription
usercreate, list, get, update, set-role, set-password, ban, impersonate, deleteUser management
sessionlist, revoke, deleteSession management

Custom Resources

These resources are MakerKit-specific:

ResourceActionsDescription
organizationslist, viewOrganization oversight
dashboardviewAdmin dashboard access
subscriptionslistSubscription viewing

Adding Custom Roles

Create restricted admin roles for support staff, moderators, or other team members:

import { defineAdminRBACConfig } from './admin/factory';
export default defineAdminRBACConfig({
// Role hierarchy - higher number = more authority
roles: {
support: 50, // Customer support staff
moderator: 30, // Community moderators
},
// Define permissions for each custom role
// Note: 'admin' automatically gets ALL permissions
permissions: {
support: {
user: ['list', 'get', 'ban'],
session: ['list', 'revoke'],
organizations: ['list', 'view'],
dashboard: ['view'],
subscriptions: ['list'],
},
moderator: {
user: ['list', 'get', 'ban'],
dashboard: ['view'],
},
},
});

Role Hierarchy

Roles are assigned numeric values indicating authority level:

  • admin: 100 - Full access (built-in, cannot be changed)
  • support: 50 - Mid-level access
  • moderator: 30 - Basic access

Higher values indicate more authority. This is useful for permission inheritance patterns if you extend the system.

Adding Custom Resources

Extend the RBAC system with your own resources:

export default defineAdminRBACConfig({
// Add custom resources
resources: {
REPORTS: 'reports',
CONTENT: 'content',
},
// Add custom actions
actions: {
EXPORT: 'export',
PUBLISH: 'publish',
MODERATE: 'moderate',
},
// Define which actions are valid for each resource
accessController: {
reports: ['list', 'view', 'export'],
content: ['list', 'view', 'publish', 'moderate'],
},
// Assign permissions to roles
permissions: {
support: {
reports: ['list', 'view'],
content: ['list', 'view'],
},
moderator: {
content: ['list', 'view', 'moderate'],
},
},
});

Server-Side Protection

Protecting Server Actions

Use withAdminPermission middleware to enforce permissions:

'use server';
import { adminActionClient, withAdminPermission } from '@kit/action-middleware';
// Require specific permission
export const banUserAction = adminActionClient
.use(withAdminPermission({ user: ['ban'] }))
.inputSchema(banUserSchema)
.action(async ({ parsedInput, ctx }) => {
// Only admins with 'user:ban' permission reach here
await banUser(parsedInput.userId);
});
// Require multiple permissions
export const manageUserAction = adminActionClient
.use(withAdminPermission({
user: ['get', 'ban'],
session: ['revoke'],
}))
.inputSchema(manageUserSchema)
.action(async ({ parsedInput, ctx }) => {
// Requires ALL specified permissions
});

Protecting Data Loaders

Use requireAdmin for server components and data loaders:

import 'server-only';
import { cache } from 'react';
import { requireAdmin } from '@kit/auth/require-admin';
export const loadReportsData = cache(async () => {
await requireAdmin(); // Redirects if not admin
return await reportsService.getAll();
});

Client-Side Permission Checks

useAdminPermissions Hook

Conditionally render UI based on permissions:

'use client';
import { useAdminPermissions } from '@kit/admin/hooks/use-admin-permissions';
function UserActions({ userId }: { userId: string }) {
const { hasPermission, isLoading } = useAdminPermissions();
if (isLoading) return <Spinner />;
return (
<div>
{hasPermission({ user: ['get'] }) && (
<Button>View User</Button>
)}
{hasPermission({ user: ['ban'] }) && (
<Button>Ban User</Button>
)}
{hasPermission({ user: ['delete'] }) && (
<Button variant="destructive">Delete User</Button>
)}
</div>
);
}

Hook API Reference

const {
role, // Current user's admin role (or null)
isAdmin, // Whether user has any admin role
isLoading, // Whether session is loading
hasPermission, // Check specific permission(s)
hasAnyPermission,// Check if user has any permission on resource
permissions, // All permissions for current role
getActionsFor, // Get all actions user can perform on resource
} = useAdminPermissions();
// Check single permission
hasPermission({ user: ['ban'] });
// Check multiple permissions (all required)
hasPermission({ user: ['ban', 'delete'] });
// Check any permission on resource
hasAnyPermission('user'); // true if user has any user permission
// Get all available actions
getActionsFor('user'); // ['list', 'get', 'ban', ...]

Role Checking Utilities

For simple admin role checks without permission granularity:

// Client-side
import { isAdminRole } from '@kit/auth/is-admin-role';
function showAdminLink(userRole: string | null) {
return isAdminRole(userRole);
}
// Server-side (non-redirecting)
import { isUserAdmin } from '@kit/auth/require-admin';
export async function getPageData() {
const isAdmin = await isUserAdmin();
return { showAdminFeatures: isAdmin };
}

Permission Matrix

Default permissions for the built-in admin role:

ResourceActions
usercreate, list, get, update, set-role, set-password, ban, impersonate, delete
sessionlist, revoke, delete
organizationslist, view
dashboardview
subscriptionslist

Better Auth Integration

The RBAC system integrates with Better Auth's admin plugin. Important naming conventions:

  • Resources must be singular: user not users, session not sessions
  • Actions must match Better Auth names: set-role not change-role, ban for both ban and unban

This ensures server actions work correctly with Better Auth's internal permission checks.

Frequently Asked Questions

Why must I use singular resource names like 'user' instead of 'users'?
Better Auth's admin plugin expects singular resource names internally. Using 'users' instead of 'user' will cause permission checks to fail silently.
Does the admin role need explicit permissions?
No. The built-in admin role (level 100) automatically receives ALL permissions. Only custom roles like support or moderator need explicit permission grants.
How do I check permissions in a React component?
Use the useAdminPermissions hook: const { hasPermission } = useAdminPermissions(); then hasPermission({ user: ['ban'] }) returns true/false.
Can I create permissions for custom resources?
Yes. Add resources and actions to defineAdminRBACConfig(), define valid actions in accessController, then assign permissions to roles.
What's the difference between requireAdmin and withAdminPermission?
requireAdmin() checks if the user has any admin role. withAdminPermission() checks for specific permissions like user:ban or session:revoke.

Previous: Organization Management

Next: Extending Admin