Admin
Role-based admin features with Better Auth and Convex.
Overview
Admin features built on Better Auth's admin plugin:
| Feature | Description |
|---|---|
| Role-based access | Admin/user roles with middleware checking |
| User management | Create, update, delete users |
| Banning | Ban/unban users with expiration |
| Session management | List, revoke user sessions |
| Impersonation | Admin can impersonate users |
| Custom permissions | Granular access control beyond roles |
Prerequisites
Ensure you have Auth Server set up before adding admin features.
1. Server Configuration
Add the admin plugin to your auth options:
import { admin } from 'better-auth/plugins';
import { defineAuth } from './generated/auth';
export default defineAuth((ctx) => ({
// ... existing config
plugins: [
admin({
defaultRole: 'user',
// adminUserIds: ['user_id_1'], // Always admin regardless of role
// impersonationSessionDuration: 60 * 60, // 1 hour default
// defaultBanReason: 'No reason',
// bannedUserMessage: 'You have been banned',
}),
// ... other plugins
],
}));Admin Assignment via Environment
Configure admin emails in your environment:
ADMIN=admin@example.com,other@example.comAssign admin role on user creation:
import { defineAuth } from './generated/auth';
export default defineAuth((ctx) => ({
// ... config
triggers: {
user: {
create: {
before: async (data, triggerCtx) => {
const env = getEnv();
const adminEmails = env.ADMIN;
const role =
data.role !== 'admin' && adminEmails?.includes(data.email)
? 'admin'
: data.role;
return { data: { ...data, role } };
},
},
},
},
}));2. Client Configuration
Add the admin client plugin:
import { adminClient } from 'better-auth/client/plugins';
export const authClient = createAuthClient({
// ... existing config
plugins: [
adminClient(),
// ... other plugins
],
});3. Schema
Add admin fields to your user table:
import { boolean, convexTable, defineSchema, integer, text } from 'kitcn/orm';
export const user = convexTable('user', {
// ... existing fields
role: text(), // 'admin' | 'user'
banned: boolean(),
banReason: text(),
banExpires: integer(),
});
export const session = convexTable('session', {
// ... existing fields
impersonatedBy: text(), // Admin user ID
});
export const tables = { user, session };
export default defineSchema(tables, { strict: false });4. Access Control
Role Middleware
Add role middleware to your cRPC setup:
const c = initCRPC
.meta<{ auth?: 'optional' | 'required'; role?: 'admin' }>()
.create();
/** Role middleware - checks admin role from meta after auth middleware */
const roleMiddleware = c.middleware<object>(({ ctx, meta, next }) => {
const user = (ctx as { user?: { role?: string | null } }).user;
if (meta.role === 'admin' && user?.role !== 'admin') {
throw new CRPCError({
code: 'FORBIDDEN',
message: 'Admin access required',
});
}
return next({ ctx });
});
export const authQuery = c.query
.meta({ auth: 'required' })
.use(devMiddleware)
.use(authMiddleware)
.use(roleMiddleware);
export const authMutation = c.mutation
.meta({ auth: 'required' })
.use(devMiddleware)
.use(authMiddleware)
.use(roleMiddleware)
.use(ratelimit.middleware());Role Guard Helper
import { CRPCError } from 'kitcn/server';
export function roleGuard(
role: 'admin',
user: { role?: string | null } | null
) {
if (!user) {
throw new CRPCError({
code: 'FORBIDDEN',
message: 'Access denied',
});
}
if (role === 'admin' && user.role !== 'admin') {
throw new CRPCError({
code: 'FORBIDDEN',
message: 'Admin access required',
});
}
}Custom Access Control
Define granular permissions beyond admin/user roles:
import { createAccessControl } from 'better-auth/plugins/access';
import { defaultStatements, adminAc } from 'better-auth/plugins/admin/access';
const statement = {
...defaultStatements,
project: ['create', 'read', 'update', 'delete'],
} as const;
export const ac = createAccessControl(statement);
export const admin = ac.newRole({
...adminAc.statements,
project: ['create', 'read', 'update', 'delete'],
});
export const user = ac.newRole({
project: ['create', 'read'],
});Pass to plugins:
// Server
admin({ ac, roles: { admin, user } })
// Client (src/lib/convex/auth-client.ts)
adminClient({ ac, roles: { admin, user } })Admin Functions
Function examples use the ORM (ctx.orm).
Check Admin Status
import { z } from 'zod';
import { authQuery } from '../lib/crpc';
export const checkUserAdminStatus = authQuery
.meta({ role: 'admin' })
.input(z.object({ userId: z.string() }))
.output(
z.object({
isAdmin: z.boolean(),
role: z.string().nullish(),
})
)
.query(async ({ ctx, input }) => {
const user = await ctx.orm.query.user.findFirstOrThrow({
where: { id: input.userId },
});
return {
isAdmin: user.role === 'admin',
role: user.role,
};
});Build additional admin functions (update role, grant admin, list users, dashboard stats) following this pattern. The key conventions:
- Gate admin functions with
.meta({ role: 'admin' })to trigger role middleware - Use
ctx.ormfor user reads and role updates - Use
.paginated()for list endpoints
See the example app for complete admin CRUD implementations.
Client Usage
React Hooks
import { authClient } from '@/lib/convex/auth-client';
function AdminPanel() {
const { data: session } = authClient.useSession();
const isAdmin = session?.user?.role === 'admin';
if (!isAdmin) {
return <div>Access denied</div>;
}
return (
<div>
{/* Admin controls */}
</div>
);
}Ban/Unban Users
// Ban user
await authClient.admin.banUser({
userId: 'user_123',
banReason: 'Violation of terms',
banExpiresIn: 60 * 60 * 24 * 7, // 7 days
});
// Unban user
await authClient.admin.unbanUser({
userId: 'user_123',
});Session Management
// List user sessions
const { data: sessions } = await authClient.admin.listUserSessions({
userId: 'user_123',
});
// Revoke specific session
await authClient.admin.revokeUserSession({
sessionToken: 'session_token',
});
// Revoke all sessions
await authClient.admin.revokeUserSessions({
userId: 'user_123',
});Impersonation
// Start impersonating user
await authClient.admin.impersonateUser({
userId: 'user_123',
});
// Stop impersonating
await authClient.admin.stopImpersonating();User Management
// Create user
await authClient.admin.createUser({
email: 'user@example.com',
password: 'password',
name: 'John Doe',
role: 'user',
});
// List users (with filtering/sorting/pagination)
const { users, total } = await authClient.admin.listUsers({
searchValue: 'john',
searchField: 'name',
limit: 20,
offset: 0,
sortBy: 'createdAt',
sortDirection: 'desc',
});
// Set role
await authClient.admin.setRole({ userId, role: 'admin' });
// Set password
await authClient.admin.setUserPassword({ userId, newPassword });
// Update user
await authClient.admin.updateUser({ userId, data: { name: 'New Name' } });
// Delete user
await authClient.admin.removeUser({ userId });Permission Checking
// Check if current user has permission (server call)
const { success } = await authClient.admin.hasPermission({
permissions: { project: ['create', 'update'] },
});
// Check role permission (client-side, no server call)
const canDelete = authClient.admin.checkRolePermission({
role: 'admin',
permissions: { project: ['delete'] },
});API Reference
| Operation | Method | Admin Required |
|---|---|---|
| Create user | authClient.admin.createUser | Yes |
| List users | authClient.admin.listUsers | Yes |
| Set role | authClient.admin.setRole | Yes |
| Set password | authClient.admin.setUserPassword | Yes |
| Update user | authClient.admin.updateUser | Yes |
| Ban user | authClient.admin.banUser | Yes |
| Unban user | authClient.admin.unbanUser | Yes |
| List sessions | authClient.admin.listUserSessions | Yes |
| Revoke session | authClient.admin.revokeUserSession | Yes |
| Revoke all sessions | authClient.admin.revokeUserSessions | Yes |
| Impersonate | authClient.admin.impersonateUser | Yes |
| Stop impersonating | authClient.admin.stopImpersonating | Yes |
| Remove user | authClient.admin.removeUser | Yes |
| Check permission | authClient.admin.hasPermission | No |
| Check role permission | authClient.admin.checkRolePermission | No |
Use Convex functions for custom admin operations. Use Better Auth client API for standard operations like user management, banning, and session management.