Nest Authbeta

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

NestAuthModule.forRoot({
  // …
  adminConsole: {
    enabled: true,
    basePath: '/auth/admin',                     // default
    secretKey: process.env.ADMIN_CONSOLE_SECRET,
    sessionDuration: '8h',
    sessionCookieName: 'nest_auth_admin',
    cookie: { secure: true, sameSite: 'lax' },
    allowAdminManagement: true,                  // can admins create more admins?
    allowPlatformUserManagement: true,           // can the console mint/grant/revoke platform users?
  },
});

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:

  1. 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.
  2. 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

POST /auth/admin/signup
Content-Type: application/json
x-nest-auth-admin-secret: <your secret>
 
{
  "email": "ops@example.com",
  "password": "…",
  "tenantId": "default"
}

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

SectionCapabilities
UsersList, search, edit, suspend, reactivate, view sessions, force logout-all, reset MFA, filter by access scope, manage platform roles
RolesCRUD, mark isSystem, assign permissions
PermissionsCRUD, group by category
TenantsCRUD, view membership, edit metadata
API KeysList, create, revoke, view last-used
SettingsInspect 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:

NestAuthModule.forRoot({
  routePrefix: 'account',                 // → /account/login, /account/mfa/...
  adminConsole: {
    enabled: true,
    secretKey: env.ADMIN_SECRET,
    path: 'manage',                       // → dashboard at /account/manage
    // basePath is the SPA / cookie base. It defaults to `/<routePrefix>/<path>`
    // (here `/account/manage`). Set it explicitly to include a global prefix:
    basePath: '/api/account/manage',
  },
});

If you change routePrefix, point your client SDK's endpoints at the new paths (the client defaults to /auth/...). routePrefix is honored by forRoot; with forRootAsync the prefix stays at the default auth.

Using app.setGlobalPrefix('api')? Routes become /api/auth/... automatically — set adminConsole.basePath to /api/auth/admin (include the global prefix) so the dashboard's API calls and cookie resolve.

Disabling

adminConsole: { enabled: false },

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/sessionSecret shorter 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 (429 after the budget); (4) the session cookie is Secure unless NODE_ENV is explicitly development or test. 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.)

adminConsole: {
  enabled: true,
  secretKey: process.env.ADMIN_CONSOLE_SECRET,     // 32+ chars — gates bootstrap signup + reset
  sessionSecret: process.env.ADMIN_SESSION_SECRET, // 32+ chars — signs admin session cookies
},

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).

EndpointBucketDefault
POST <admin>/loginadminLogin5 / 60s
POST <admin>/signup, POST <admin>/reset-passwordadminReset5 / 15 min

Over budget returns 429. Tune the windows via the shared rate-limit buckets, or opt out entirely:

security: {
  rateLimit: {
    buckets: {
      adminLogin: { windowMs: 60_000, max: 10 },
      adminReset: { windowMs: 15 * 60_000, max: 5 },
    },
  },
},
adminConsole: {
  bruteForce: { enabled: false }, // opt out (not recommended)
},

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).

The admin session cookie is Secure unless NODE_ENV is explicitly development or test.

  • Before: Secure only when NODE_ENV === 'production' — so staging, an unset env, or a misconfigured prod shipped the cookie in cleartext.
  • After: anything other than development/test fails safe to Secure.

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:

adminConsole: {
  cookie: { secure: false }, // only for real HTTP
},

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: DENY
  • Content-Security-Policy with frame-ancestors 'none' (a <meta> CSP cannot set frame-ancestors, so it must be a real response header), plus default-src 'self', object-src 'none', base-uri 'self', form-action 'self'
  • X-Content-Type-Options: nosniff
  • Referrer-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.

adminConsole: {
  allowPublicSignupAfterFirstAdmin: false, // default — bootstrap-only
  allowAdminManagement: true,              // default — create/manage admins in the UI
},

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.

Keeping platform users out of the console

When platformAccess.enabled is on, the console can create platform (super-admin) users, grant or revoke their access and set their platform roles. If your app manages platform users in its own UI, turn that off so the console secret can't make an account a super-admin:

adminConsole: {
  allowPlatformUserManagement: false, // default: true
},

With it off, the console hides the platform controls and its user endpoints answer 403 PLATFORM_USER_MANAGEMENT_DISABLED to isPlatformUser and platformRoleIds. Existing platform users are still listed and viewable, but read-only: editing, deleting or removing the MFA device of one is refused too, since a password or email change there would be an account takeover by another route. Tenant users, sessions, roles and tenants stay fully manageable.

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 scoped ValidationPipe, 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.

security: {
  csrf: { enabled: true }, // admin SPA echoes the token automatically
},

Recommended whenever you serve admin (or app) auth over cookies.