# Admin Plugin

> User management, role-based access control, banning, and impersonation for administrators.

*Canonical: https://makerkit.dev/docs/nextjs-drizzle/better-auth/admin-plugin*

---

This guide is part of the [Drizzle](/drizzle) stack docs.

Manage users from an admin dashboard. The admin plugin provides role-based access control, user banning, session management, and impersonation for support and debugging.

This page is part of the [Authentication documentation](./overview).

## Overview

The admin plugin extends Better Auth with administrative capabilities:

- **Role-based access control (RBAC)** - Define admin roles with specific permissions
- **User management** - View, search, and manage all users
- **Banning** - Temporarily or permanently ban users
- **Impersonation** - Sign in as another user for support
- **Session control** - Revoke user sessions

## Configuration

The admin plugin is configured in `packages/better-auth/src/plugins/admin.ts`:

```typescript
import { admin } from 'better-auth/plugins/admin';
import { userAc } from 'better-auth/plugins/admin/access';
import {
  adminAc,
  adminRoles,
  getAdminRoleNames,
} from '@kit/rbac/admin';
import { DEFAULT_ROLE } from './admin-config';

const IMPERSONATION_SESSION_DURATION = 60 * 60; // 1 hour
const DEFAULT_BAN_EXPIRES_IN = undefined; // Permanent

function buildRolesConfig() {
  const roles = { user: userAc };

  for (const [roleName, role] of Object.entries(adminRoles)) {
    roles[roleName] = role;
  }

  return roles;
}

export const adminPlugin = admin({
  defaultRole: DEFAULT_ROLE,
  adminRoles: getAdminRoleNames(),
  impersonationSessionDuration: IMPERSONATION_SESSION_DURATION,
  defaultBanExpiresIn: DEFAULT_BAN_EXPIRES_IN,
  ac: adminAc,
  roles: buildRolesConfig(),
});
```

## RBAC Configuration

Admin roles are defined in `packages/rbac/src/admin-rbac.config.ts` via `defineAdminRBACConfig()`. The kit ships a single admin role:

| Role | Hierarchy level | Description |
|------|-----------------|-------------|
| `user` | — | Regular user (non-admin), Better Auth's `defaultRole` |
| `admin` | 100 | Full administrative access (every action on every resource) |

Higher hierarchy numbers mean more authority. There is no `super-admin` role by default: `admin` already holds all permissions.

### Resources and Actions

The default access controller (`packages/rbac/src/admin/defaults.ts`) defines these resources and actions. The `user` and `session` names must match Better Auth exactly:

| Resource | Actions |
|----------|---------|
| `user` | `create`, `list`, `get`, `update`, `set-role`, `set-password`, `ban`, `impersonate`, `delete` |
| `session` | `list`, `revoke`, `delete` |
| `organizations` | `list`, `view` |
| `dashboard` | `view` |

Note the exact names: there is no `read` (use `get` or `list`) and no `revokeAll` (use `revoke`). `ban` covers both ban and unban.

### Customizing Admin Roles

Edit `packages/rbac/src/admin-rbac.config.ts` to add roles, resources, or actions:

```typescript
import { defineAdminRBACConfig } from './admin/factory';

export default defineAdminRBACConfig({
  // Custom roles with hierarchy levels (admin: 100 is always present)
  roles: {
    support: 50,
    moderator: 30,
  },

  // Optional: extra resources and the actions they accept
  resources: {
    SUBSCRIPTIONS: 'subscriptions',
  },
  actions: {
    REFUND: 'refund',
  },
  accessController: {
    subscriptions: ['list', 'view', 'refund'],
  },

  // Permissions per role. 'admin' gets everything unless overridden.
  permissions: {
    support: {
      user: ['list', 'get', 'ban'],
      session: ['list', 'revoke'],
      organizations: ['list', 'view'],
      dashboard: ['view'],
      subscriptions: ['list'],
    },
    moderator: {
      user: ['list', 'get', 'ban'],
      dashboard: ['view'],
    },
  },
});
```

The factory validates the compiled config and throws `AdminRBACConfigError` at import time on duplicate resource/action values, permissions using actions missing from the access controller, or permissions defined for roles missing from the hierarchy.

Custom `accessController` entries are unioned with the defaults, so listing a subset of a default resource's actions adds to them and never removes them. `adminAc` and `adminRoles` (consumed by the admin plugin above) are derived from this compiled config.

## Admin Dashboard

The admin panel is available at `/admin` for users with admin roles. Features include:

- **User list** - Paginated view of all users
- **User search** - Find users by email or name
- **User details** - View account information, sessions, organizations
- **User actions** - Ban, unban, impersonate, revoke sessions

See the [Admin Overview](../admin/overview) documentation for detailed admin panel usage.

## User Management

### Listing Users

```typescript
import { authClient } from '@kit/better-auth/client';

const { data: users } = await authClient.admin.listUsers({
  limit: 20,
  offset: 0,
  sortBy: 'createdAt',
  sortOrder: 'desc',
});
```

### Searching Users

```typescript
const { data: results } = await authClient.admin.listUsers({
  searchField: 'email',
  searchValue: 'user@example.com',
});
```

## Banning Users

### Ban a User

```typescript
import { authClient } from '@kit/better-auth/client';

// Permanent ban
await authClient.admin.banUser({
  userId: 'user-id',
  reason: 'Violation of terms of service',
});

// Temporary ban (7 days)
await authClient.admin.banUser({
  userId: 'user-id',
  reason: 'Suspicious activity',
  expiresIn: 7 * 24 * 60 * 60, // seconds
});
```

### Unban a User

```typescript
await authClient.admin.unbanUser({
  userId: 'user-id',
});
```

### Ban Behavior

When banned:
- User's active sessions are revoked
- User cannot sign in
- User sees a "banned" message on sign-in attempt
- Temporary bans auto-expire

## Impersonation

Impersonation allows admins to sign in as another user for support and debugging without knowing their password.

### Start Impersonation

```typescript
import { authClient } from '@kit/better-auth/client';

await authClient.admin.impersonateUser({
  userId: 'user-id',
});

// Now signed in as that user
// Session duration: 1 hour (configurable)
```

### Stop Impersonation

```typescript
await authClient.admin.stopImpersonation();

// Returns to admin account
```

### Impersonation Safety

- Impersonation sessions expire after 1 hour
- Actions during impersonation are logged
- The impersonated session is clearly marked
- Cannot impersonate other admins (configurable)

## Session Management

### View User Sessions

```typescript
const { data: sessions } = await authClient.admin.listUserSessions({
  userId: 'user-id',
});

// Returns: session ID, device info, IP, created at, last active
```

### Revoke a Session

```typescript
await authClient.admin.revokeSession({
  sessionId: 'session-id',
});
```

### Revoke All Sessions

```typescript
await authClient.admin.revokeUserSessions({
  userId: 'user-id',
});
```

## Role Assignment

### Set User Role

```typescript
await authClient.admin.setRole({
  userId: 'user-id',
  role: 'admin',
});
```

### Check User Role

```typescript
import { isAdminRole } from '@kit/better-auth/admin-config';

const user = await authClient.getSession();

if (isAdminRole(user?.data?.user?.role)) {
  // User has admin privileges
}
```

## Client-Side Role Checks

The `admin-config.ts` exports utilities for role checking:

```typescript
import {
  ADMIN_ROLES,
  isAdminRole,
  roleHasPermission,
  getRolePermissions,
} from '@kit/better-auth/admin-config';

// Check if role is an admin role (any role in the admin hierarchy)
if (isAdminRole('admin')) {
  // true
}

// Check specific permission
if (roleHasPermission('admin', 'user', 'ban')) {
  // Admin can ban users
}

// Get all permissions for a role
const permissions = getRolePermissions('admin');
```

## Security Considerations

- **Principle of least privilege** - Assign minimal necessary permissions
- **Audit logging** - Log all admin actions for accountability
- **Two admin rule** - Consider requiring multiple admins for destructive actions
- **Impersonation limits** - Restrict what actions can be taken while impersonating

## Common Pitfalls

- **Missing role checks in UI** - Always check permissions before showing admin features.
- **Impersonation without logging** - Ensure impersonation actions are auditable.
- **Overly permissive roles** - Start restrictive and add permissions as needed.
- **Forgetting client plugin** - Add `adminClient()` to `auth-client.ts` for client-side admin methods.

{% faq
   title="Frequently Asked Questions"
   items=[
     {"question": "How do I create the first admin user?", "answer": "Use the database directly to set a user's role to 'admin' (the only admin role shipped by default). After that, existing admins can promote other users."},
     {"question": "Can I customize what admins can impersonate?", "answer": "Yes. Modify the RBAC configuration to control which roles can impersonate and which roles can be impersonated."},
     {"question": "How do I audit admin actions?", "answer": "Implement logging in your admin action handlers. The kit includes logging infrastructure you can extend."},
     {"question": "Can I have multiple admin levels?", "answer": "Yes. Add roles with their own hierarchy levels and permission sets in admin-rbac.config.ts, alongside the built-in 'admin' (level 100). Common additions are support and moderator."},
     {"question": "What happens to a user's data when banned?", "answer": "Banning only prevents sign-in. User data remains intact. For data deletion, use account deletion which triggers cleanup hooks."}
   ]
/%}

---

For detailed admin panel documentation, see [Admin Overview](../admin/overview).
