Admin Guide
UniAuth includes a comprehensive admin panel for managing users, roles, OAuth clients, webhooks, and system settings. The admin panel is accessible at /admin and requires the admin or moderator role.
Admin Panel Overview
The admin panel provides a sidebar navigation with the following sections:
- Dashboard — Overview statistics (total users, active sessions, recent registrations)
- Users — User listing, search, and management
- Sessions — Active sessions across all users
- Activity — System-wide activity and audit log
- Roles — Role management and permissions
- OAuth Clients — Manage registered OAuth applications
- Webhooks — Configure event notifications
- Analytics — Detailed usage analytics and trends
- Settings — System configuration
The admin panel is protected at multiple levels: the Next.js middleware checks for admin/moderator roles on all /admin/* routes, and each API endpoint additionally verifies the role via the verifyAdmin() helper function.
User Management
The user management page at /admin/users provides a searchable, paginated list of all users in the system.
User List
The user list displays each user's email, name, role, email verification status, 2FA status, creation date, and last login. You can search by email or name and filter by role.
User Details
Clicking on a user opens their detail view where administrators can:
- View all user information (profile, security settings, connected services)
- Change the user's role (admin, moderator, user)
- Lock or unlock the user's account
- Reset the user's failed login attempts
- Force email verification
- Delete the user account
API Endpoints
// List users with pagination and search
GET /api/admin/users?page=1&limit=20&search=john&role=admin
// Get user details
GET /api/admin/users/:id
// Update user (change role, lock/unlock)
PUT /api/admin/users/:id
{
"role": "moderator",
"locked_until": null // Unlock account
}
// Delete user
DELETE /api/admin/users/:idRole Management
UniAuth uses a role-based access control (RBAC) system with three default roles:
| Role | Description | Permissions | System |
|---|---|---|---|
| admin | Full system access | users:read, users:write, users:delete, sessions:read, sessions:revoke, logs:read, roles:read, roles:write, stats:read, oauth:read, oauth:write | Yes |
| moderator | Limited management access | users:read, sessions:read, logs:read, stats:read | Yes |
| user | Regular user | None (self-service only) | Yes |
System roles cannot be deleted but their permissions can be modified. Administrators can create custom roles with specific permission sets via the roles management page at /admin/roles.
Available Permissions
| Permission | Description |
|---|---|
users:read | View user list and details |
users:write | Modify user accounts (change role, lock, etc.) |
users:delete | Delete user accounts |
sessions:read | View active sessions |
sessions:revoke | Revoke user sessions |
logs:read | View activity logs |
roles:read | View roles |
roles:write | Create and modify roles |
stats:read | View dashboard statistics and analytics |
oauth:read | View OAuth clients |
oauth:write | Create, modify, and delete OAuth clients |
Creating a Custom Role
POST /api/admin/roles
Content-Type: application/json
{
"name": "support",
"description": "Support team — can view users and logs but cannot modify",
"permissions": ["users:read", "logs:read", "sessions:read"]
}OAuth Client Management
The OAuth client management page at /admin/oauth-clients shows all registered OAuth applications across all users. Administrators have full control over every client.
Client Properties
- Client Name — Displayed on the consent screen when users authorize the app
- Client ID — Public identifier for the application
- Client Secret — Secret credential (stored as SHA-256 hash, shown only once at creation)
- Redirect URIs — Allowed callback URLs (strictly validated during authorization)
- Allowed Scopes — Which scopes the client can request (openid, profile, email, phone, address)
- Grant Types — Permitted OAuth flows (authorization_code, refresh_token, client_credentials, device_code)
- Token Endpoint Auth Method — How the client authenticates at the token endpoint (client_secret_post, client_secret_basic)
- Post-Logout Redirect URIs — Allowed URIs for RP-Initiated Logout
- Back-Channel Logout URI — URL for receiving back-channel logout notifications
- SCIM Token — Token for SCIM provisioning (if enabled for this client)
- Is First-Party — First-party clients can skip the consent screen
- Is Active — Toggle to enable/disable the client without deleting it
First-Party Clients
Administrators can mark clients as "first-party," which means they are owned by the same organization as the UniAuth instance. First-party clients skip the consent screen during authorization, providing a seamless login experience for your own applications. Only administrators can set this flag.
OIDC Key Rotation
UniAuth uses an RS256 keypair for signing ID tokens. The private key is encrypted with AES-256-GCM and stored in the system_settings table. Key rotation can be performed via the admin panel or API:
POST /api/admin/oidc-keys
// Generates a new RS256 keypair:
// 1. Creates a new 2048-bit RSA key pair
// 2. Encrypts the private key with AES-256-GCM
// 3. Stores both keys in system_settings
// 4. The JWKS endpoint immediately serves the new public key
// Response:
{
"success": true,
"message": "OIDC keys rotated successfully"
}Important: After key rotation, previously issued ID tokens will fail signature verification against the new public key. Clients that cache the JWKS should refresh their key cache. Access tokens (HS256) are unaffected by key rotation.
It is recommended to rotate OIDC keys periodically (e.g., every 90 days) as a security best practice. The initial keypair is generated automatically at server startup if none exists.
Webhook Management
Webhooks allow UniAuth to notify external services when events occur. Configure webhooks at /admin/webhooks.
Supported Events
| Event | Description |
|---|---|
user.created | A new user account was created |
user.updated | A user's profile was updated |
user.deleted | A user account was deleted |
login.success | A successful login occurred |
login.failed | A failed login attempt occurred |
password.changed | A user changed their password |
2fa.enabled | A user enabled two-factor authentication |
2fa.disabled | A user disabled two-factor authentication |
Webhook Payload
Each webhook delivery is a POST request with a JSON payload:
POST https://your-service.com/webhook
Content-Type: application/json
X-Webhook-Signature: sha256=HMAC_SIGNATURE
{
"event": "user.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"userId": "uuid",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe"
}
}Signature Verification
Every webhook delivery includes an X-Webhook-Signature header containing an HMAC-SHA256 signature of the payload using the webhook's secret. Always verify this signature before processing the event:
// Node.js signature verification
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your webhook handler:
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
req.headers['x-webhook-signature'],
'your-webhook-secret'
);Delivery and Retries
UniAuth tracks delivery status for each webhook event. Failed deliveries are retried with exponential backoff. You can view delivery history including response status codes, timestamps, and retry counts in the admin panel.
Testing Webhooks
You can send a test event to any webhook to verify connectivity:
POST /api/admin/webhooks/:id/test
// Sends a test ping event to the webhook URL.
// Response includes delivery status and any error messages.Analytics Dashboard
The analytics dashboard at /admin/analytics provides detailed insights into user activity and system health.
Available Metrics
- User Growth — New registrations over time (daily, weekly, monthly)
- Login Activity — Successful and failed login attempts over time
- Active Sessions — Number of concurrent active sessions
- 2FA Adoption — Percentage of users with 2FA enabled, broken down by method
- OAuth Usage — Authorization requests and token issuance by client
- Security Events — Account lockouts, password resets, suspicious login attempts
- Top Activity Types — Most common actions in the system
Dashboard Statistics API
// Quick stats for the dashboard
GET /api/admin/stats
// Response:
{
"totalUsers": 1250,
"activeSessionCount": 342,
"recentRegistrations": 15, // Last 7 days
"recentLogins": 890, // Last 7 days
"lockedAccounts": 3,
"unverifiedEmails": 45
}
// Detailed analytics with date range
GET /api/admin/analytics?period=30d
// Returns time-series data for charts and graphs.User Impersonation
Administrators can impersonate any user to see the application from their perspective. This is useful for debugging user-reported issues and verifying account settings.
How Impersonation Works
- The admin clicks "Impersonate" on a user in the admin panel.
- The system creates a signed impersonation token containing the admin's ID and the target user's ID.
- This token is stored in a separate cookie alongside the admin's existing auth cookie.
- While impersonating, the admin sees the application as the target user, but a yellow banner is displayed at the top of every page indicating impersonation mode.
- The admin can stop impersonating at any time by clicking "Stop Impersonating" in the banner.
Safety Measures
- Only users with the
adminrole can impersonate - The admin's own session is preserved (they are not logged out)
- Impersonation tokens have a short expiry
- All impersonation actions are logged in the activity log
- The impersonation banner cannot be hidden or dismissed — it is always visible
// Start impersonation
POST /api/admin/impersonate
{
"targetUserId": "uuid-of-user-to-impersonate"
}
// Stop impersonation
POST /api/admin/impersonate/stopSystem Settings
System settings are managed via the settings page at /admin/settings or the API. Settings are stored in the system_settings table as key-value pairs.
Configurable Settings
- SMTP Configuration — Email server settings (host, port, user, password, TLS). When not configured, emails are logged to the console instead of being sent.
- Email Templates — Customize the HTML templates for verification emails, password reset emails, OTP codes, and welcome emails.
- Security Policies — Session timeout, maximum concurrent sessions, password requirements, lockout thresholds.
- Branding — Application name and logo displayed on login, consent, and email templates.
Stored Keys
The system_settings table also stores cryptographic material generated at server startup:
- ML-DSA-44 keypair — Post-quantum digital signature keys for session signing
- ML-KEM-768 keypair — Post-quantum key encapsulation keys (infrastructure for future key rotation)
- OIDC RS256 keypair — RSA keys for signing ID tokens (private key encrypted with AES-256-GCM)
These keys are initialized automatically via the instrumentation.ts hook when the server starts. If keys already exist in the database, they are loaded; otherwise, new keys are generated.
Promoting the First Admin
When setting up UniAuth for the first time, you need to promote at least one user to the admin role. This can be done directly in the database:
-- Promote a user to admin by email
UPDATE users SET role = 'admin' WHERE email = '[email protected]';After the first admin is created, they can promote other users through the admin panel without direct database access.
Activity Logs
UniAuth maintains a comprehensive audit trail of all security-relevant events. The activity log is accessible at /admin/activity.
Logged Events
- User registration and email verification
- Login attempts (successful and failed)
- Password changes and resets
- 2FA setup, verification, and disabling
- Passkey registration and authentication
- Session creation and revocation
- OAuth consent grants
- Profile updates
- Account deletion
- Admin actions (role changes, impersonation, etc.)
Each log entry includes the user ID, IP address, User-Agent, timestamp, and optional metadata (stored as JSONB). Activity logs older than 1 year are automatically purged by the data retention scheduler.
GET /api/admin/activity?page=1&limit=50&type=login.failed&userId=uuid
// Response:
{
"activities": [
{
"id": "uuid",
"userId": "uuid",
"activityType": "login.failed",
"description": "Failed login attempt",
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0 ...",
"metadata": { "reason": "invalid_password" },
"createdAt": "2024-01-15T10:30:00Z"
}
],
"total": 156,
"page": 1,
"limit": 50
}