Admin Console
The embedded admin dashboard.
Nest Auth ships with a React-based admin console you can mount alongside your API. It gives non-engineers a UI for managing users, roles, permissions, tenants, and API keys without writing custom screens.
Enabling
The console mounts at /auth/admin/* (configurable). UI assets are served from the library — you don't need to build or deploy anything separately.
The dual-purpose secretKey
adminConsole.secretKey gates two things:
- The admin signup endpoint — only requests carrying this secret in the right header can create the first admin user. Once you've bootstrapped, hide or rotate this secret.
- The dashboard cookie session — used as part of the signing material so an admin session can't be forged from a leaked DB row alone.
Treat it like a JWT secret: keep it out of source, rotate when an operator leaves.
Bootstrapping the first admin
After the first admin exists, future admins are created from inside the dashboard (or via the same endpoint while allowAdminManagement: true).
What the console does
| Section | Capabilities |
|---|---|
| Users | List, search, edit, suspend, reactivate, view sessions, force logout-all, reset MFA |
| Roles | CRUD, mark isSystem, assign permissions |
| Permissions | CRUD, group by category |
| Tenants | CRUD, view membership, edit metadata |
| API Keys | List, create, revoke, view last-used |
| Settings | Inspect resolved config (read-only — config still lives in your code) |
Auth flow inside the console
The console uses AdminSessionGuard, a separate session table (nest_auth_admin_users), and a separate cookie. Admin sessions don't grant access to your app's regular API — they're scoped to the console only.
This is deliberate: an ops person should not gain a logged-in user session just by being an admin.
Customizing the UI
Out of scope for the library — the console UI is fixed. If you need a custom admin experience, use the public services (UserService, RoleService, TenantService, AccessKeyService) and build your own screens.
Customizing paths
All auth routes share a base prefix, auth by default (POST /auth/login). Change it with routePrefix, and relocate the dashboard with adminConsole.path:
If you change
routePrefix, point your client SDK's endpoints at the new paths (the client defaults to/auth/...).routePrefixis honored byforRoot; withforRootAsyncthe prefix stays at the defaultauth.
Using
app.setGlobalPrefix('api')? Routes become/api/auth/...automatically — setadminConsole.basePathto/api/auth/admin(include the global prefix) so the dashboard's API calls and cookie resolve.
Disabling
Or omit the section entirely. None of the routes get registered, none of the assets get served.
Security
The admin console is the highest-value surface in the product, so 2.8.0 hardens it by default. Most of this is automatic; a few items are breaking on upgrade.
Upgrading from 2.7.x? Four changes affect a running app: (1) a
secretKey/sessionSecretshorter than 32 chars now throws at boot; (2) admin sessions minted by older versions are invalidated — admins re-login once; (3) admin login is rate-limited out of the box (429after the budget); (4) the session cookie isSecureunlessNODE_ENVis explicitlydevelopmentortest. Details below.
Strong secrets, fail-closed
adminConsole.secretKey and the recommended, dedicated adminConsole.sessionSecret must be a high-entropy value of 32+ characters. A short or known-weak value (admin, changeme, secret, password, …) now throws at boot instead of just warning — consistent with the fail-closed session.jwt.secret. If you can't supply a strong key, disable the console (adminConsole.enabled: false) rather than shipping a guessable one. (Leaving secretKey unset simply disables the console.)
Keep the two separate. secretKey travels on the wire to the signup/reset endpoints; reusing it as the cookie-signing key would turn a captured or guessed setup key into a session-forging primitive.
If you leave sessionSecret unset, the signing key is derived from secretKey (sha256("nest-auth-admin-session:" + secretKey)) rather than using it raw. That derivation is new in 2.8.0, so admin sessions signed by an older version become invalid on upgrade — admins simply log in once more. A dedicated sessionSecret (used verbatim) is still strongly recommended.
Brute-force protection (on by default)
Admin auth endpoints are throttled out of the box — you do not need security.rateLimit.enabled. This caps online password/secret guessing and bounds the per-attempt argon2 work (a request-to-work DoS-amplification vector).
| Endpoint | Bucket | Default |
|---|---|---|
POST <admin>/login | adminLogin | 5 / 60s |
POST <admin>/signup, POST <admin>/reset-password | adminReset | 5 / 15 min |
Over budget returns 429. Tune the windows via the shared rate-limit buckets, or opt out entirely:
If you also enable security.lockout, admin login participates in the soft lockout: repeated failures for an identifier trip the lock, and a successful login clears the counter. Login also no longer leaks valid admin emails — a missing admin runs a dummy argon2 verify, so a wrong password and an unknown admin are indistinguishable (identical 401).
Secure cookie by default
The admin session cookie is Secure unless NODE_ENV is explicitly development or test.
- Before:
Secureonly whenNODE_ENV === 'production'— so staging, an unset env, or a misconfigured prod shipped the cookie in cleartext. - After: anything other than
development/testfails safe toSecure.
TLS terminated at a proxy is unaffected (the browser leg is still HTTPS), and a sameSite: 'none' cookie always forces Secure. For a genuinely-HTTP deployment, opt out explicitly:
Anti-clickjacking and response headers
Every admin route — the SPA shell and all admin API controllers — sets defense-in-depth headers so a logged-in admin can't be framed and UI-redressed into a destructive action. These are always on, no configuration:
X-Frame-Options: DENYContent-Security-Policywithframe-ancestors 'none'(a<meta>CSP cannot setframe-ancestors, so it must be a real response header), plusdefault-src 'self',object-src 'none',base-uri 'self',form-action 'self'X-Content-Type-Options: nosniffReferrer-Policy: no-referrer
Bootstrap-only signup
The public, secret-key-gated POST <admin>/signup is bootstrap-only: once one admin exists it's closed, and further admins are created by a signed-in admin from the dashboard (a session-guarded endpoint). This stops a leaked secretKey from minting unlimited super-admins. Post-bootstrap, every attempt returns the same ADMIN_BOOTSTRAP_CLOSED regardless of whether the key is right — so there's no correct/incorrect secret-key oracle to grind.
Set allowPublicSignupAfterFirstAdmin: true only to restore the legacy "additional admins via the shared secret key" behaviour. allowAdminManagement: false turns off admin creation/management entirely — the signup endpoint then also refuses with ADMIN_MANAGEMENT_DISABLED.
Admin account safety
- Last-admin guard — deleting the final remaining admin is refused (
409 ADMIN_LAST_REMAINING). It would otherwise lock everyone out, since the management API is session-guarded and secret-key bootstrap is already closed. - Revoke on password change — changing an admin's password (dashboard update or the reset flow) bumps its token version, invalidating that admin's outstanding session cookies.
- Password floor — admin passwords are held to a minimum of 8 characters through every path (even a direct service call), independent of any global
ValidationPipe. The admin controllers also apply their own scopedValidationPipe, so the DTO complexity rules and mass-assignment stripping hold regardless of the consumer's pipe.
CSRF
The console is CSRF-ready. When you enable the built-in double-submit protection, the admin SPA reads the non-httpOnly CSRF cookie and echoes it as x-csrf-token on every state-changing request, and admin login issues that token — so turning it on doesn't break the dashboard. It's a no-op while disabled.
Recommended whenever you serve admin (or app) auth over cookies.
Related
- Backend services reference — the same services the console uses.
- API Keys.