Roles & Permissions
UniAuth uses role-based access control (RBAC) to govern what each user can see and do across the platform. Every user is assigned a single role, and that role carries a set of granular permissions that determine access to admin features, API endpoints, and protected resources.
Overview
UniAuth ships with three built-in roles that cover the most common access patterns. Administrators can also create custom roles with tailored permission sets to match their organization's needs. Roles are stored in the roles table and referenced by name in each user record.
Built-in Roles
| Role | Description | Access Level |
|---|---|---|
| admin | Full system access | Can manage all users, roles, sessions, OAuth clients, webhooks, analytics, and system settings. Can impersonate users and rotate OIDC keys. |
| moderator | Read-only management access | Can view users, sessions, activity logs, and dashboard statistics. Cannot modify accounts, change roles, or manage OAuth clients. |
| user | Standard user (default) | Self-service access only. Can manage their own profile, security settings, connected services, and register OAuth applications via the developer console. |
Built-in roles are marked as system roles and cannot be deleted. However, administrators can modify their permission sets if the defaults do not fit their requirements.
Permissions
Permissions are stored as a JSON array on each role and follow a resource:action naming convention. The complete list of available permissions:
| Permission | Description | Admin | Moderator |
|---|---|---|---|
users:read | View user list and details | Yes | Yes |
users:write | Modify user accounts (change role, lock/unlock) | Yes | No |
users:delete | Delete user accounts | Yes | No |
sessions:read | View active sessions | Yes | Yes |
sessions:revoke | Revoke any user's sessions | Yes | No |
logs:read | View activity and audit logs | Yes | Yes |
roles:read | View roles and their permissions | Yes | No |
roles:write | Create, modify, and delete custom roles | Yes | No |
stats:read | View dashboard statistics and analytics | Yes | Yes |
settings:read | View system configuration | Yes | No |
settings:write | Modify system settings (SMTP, branding, policies) | Yes | No |
oauth:read | View all registered OAuth clients | Yes | No |
oauth:write | Create, modify, and delete OAuth clients | Yes | No |
Role in JWT Tokens
The user's role is embedded in their session JWT, making it available for server-side authorization checks without additional database queries. The JWT payload includes:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Admin",
"role": "admin",
"iat": 1700000000,
"exp": 1700604800
}This allows your application code or middleware to make authorization decisions based on the role claim without hitting the database on every request.
Middleware Protection
All routes under /admin/* are automatically protected by the UniAuth middleware. The middleware performs two checks:
- Authentication — The request must include a valid session cookie. Unauthenticated requests are redirected to the login page.
- Role check — The user's role (from the JWT) must be
adminormoderator. Users with insufficient permissions receive a 403 response.
This protection runs at the edge before your page or API handler executes, so unauthorized requests are blocked with minimal latency.
API Role Checks
In addition to middleware-level protection, individual API route handlers verify the caller's role using the verifyAdmin() helper function. This provides defense in depth:
// Example: Admin API route handler
import { verifyAdmin } from "@/lib/admin";
import { successResponse, errorResponse } from "@/lib/api-response";
export async function GET(request: Request) {
// verifyAdmin() checks the session cookie and confirms
// the user has admin or moderator role
const adminUser = await verifyAdmin();
if (!adminUser) {
return errorResponse("Unauthorized", 403);
}
// Proceed with admin-only logic
const data = await fetchSensitiveData();
return successResponse(data);
}For more granular checks, you can inspect the user's specific permissions by loading the role from the database and checking the permissions array.