Nest Authbeta

Changelog

What's new in each release of @ackplus/nest-auth.

All packages release together at the same version: the five npm packages (@ackplus/nest-auth, -client, -react, -react-native, -contracts) plus nest_auth_flutter on pub.dev. Current stable: 2.8.0.


2.8.0 — security hardening

The P0 security-hardening release: ~24 fixes across the auth core and the embedded admin console. A handful change how existing apps boot or authenticate — read the Breaking items and the 2.7.x → 2.8.0 upgrade steps before you bump. Everything under Added (opt-in) is off by default, so nothing else changes until you enable it.

  • Breaking: session.jwt.secret is now required. The shipped default ('secret') is gone — boot throws when the signing key is missing or a known-insecure value. A short (<32-char) secret warns; set session.jwt.validateSecretStrength: true to make that a hard error too.

  • Breaking: the jwt login provider is now opt-in. It used to register automatically whenever session.jwt existed, and it trusts any token signed with your secret and mints a session for its sub — a privileged bypass that must be enabled deliberately with session.jwt.enableLoginProvider: true. As defense-in-depth the auth guard now only accepts type: 'access' tokens, so a refresh token presented as a Bearer is rejected.

  • Breaking (admin console): a secretKey / sessionSecret under 32 chars (or a known-weak value) now throws at boot instead of warning. Disable the console if you can't supply a strong key.

  • Breaking (admin console): when no dedicated sessionSecret is set, the admin session-signing key is now derived from secretKey rather than being the raw secretKey. Admin sessions minted by older versions are invalidated — admins re-login once after upgrade.

  • Breaking (admin console): admin login is throttled by default (429 after ~5/min). Opt out with adminConsole.bruteForce.enabled: false.

  • Breaking (admin console): the admin session cookie is now Secure unless NODE_ENV is explicitly development or test (was: Secure only when NODE_ENV === 'production', so staging / unset / misconfigured prod shipped it in cleartext). Force it off with adminConsole.cookie.secure: false.

  • Breaking (admin console): admin DTO validation and an 8-char admin-password floor are now enforced regardless of whether your app registers a global ValidationPipe (those DTO rules were previously inert without one).

  • Breaking (social): account-linking now requires a verified provider email. A social identity whose email matches an existing local account no longer silently attaches unless the provider verified that email — the user must sign in with their existing method and link deliberately. Restore the old behavior per-provider with social.requireVerifiedEmailForLinking: false.

  • Added (opt-in): built-in CSRF for cookie-authenticated, state-changing requests via security.csrf.enabled — a double-submit token (non-httpOnly cookie the SPA echoes back in a header) plus an optional allowedOrigins check. No-op for bearer/header auth; required if you set sameSite: 'none'.

  • Added (opt-in): rate limiting for sensitive endpoints (login, signup, forgot-password, passwordless/OTP send + verify, MFA verify) via security.rateLimit.enabled. Supply a shared store for multi-instance deployments.

  • Added (opt-in): password strength policy + HIBP breach check (password.policy), enforced uniformly at every password-set path (signup, change, reset, admin-set) and on admin-console passwords. Turn on the breach check with password.policy.checkBreached: true (Have I Been Pwned k-anonymity, fail-open).

  • Added (opt-in): email-verification gatingregistration.requireVerifiedEmail: true hard-blocks a signed-in-but-unverified user from protected routes.

  • Added (opt-in): soft account lockout (security.lockout.enabled) keyed by identifier + IP so an attacker can't lock a victim's logins from another IP, plus a provider-agnostic CAPTCHA hook (security.captcha.verify) for abuse-prone routes.

  • Added (opt-in): disposable / throwaway email-domain screening at sign-up (emailAuth.disposable) — a DB-backed blocklist seedable from a built-in ~8k default list and managed from a new Blocked Emails console page. block mode rejects with 403 EMAIL_DOMAIN_NOT_ALLOWED; flag mode allows the sign-up but emits an event.

  • Hardened (admin console): signup is now bootstrap-only — once the first admin exists, the secret-key POST <admin>/signup is refused and further admins must be created by a signed-in admin from the dashboard, so a leaked secretKey can't mint unlimited super-admins. Restore the legacy shared-key path with adminConsole.allowPublicSignupAfterFirstAdmin: true.

  • Hardened (admin console): revocable admin sessions — a dashboard password change now bumps tokenVersion and revokes that admin's outstanding session cookies (previously only the secret-key reset flow did).

  • Hardened (admin console): anti-framing + transport headers on every admin route — X-Frame-Options: DENY, a real Content-Security-Policy with frame-ancestors 'none' (a <meta> CSP can't set that, so a logged-in admin was frameable), X-Content-Type-Options: nosniff, and Referrer-Policy: no-referrer — and the injected window.__NEST_AUTH_CONFIG__ JSON is escaped so no config value can break out of the inline <script>.

  • Hardened (admin console): the secret-key-gated signup / reset-password endpoints are throttled and de-oracled — the bootstrap-closed check now runs before the key comparison, so after bootstrap a wrong vs. correct key are byte-identical (the key-grinding oracle is gone). Admin login also runs a dummy Argon2 verify on the not-found path, killing the email-enumeration timing side-channel.

  • Hardened (admin console): a last-admin delete guard (409 ADMIN_LAST_REMAINING) so you can't lock everyone out, ArrayMaxSize(1000) + per-item caps on bulk blocked-domain add, and the SPA now echoes the CSRF double-submit token so the dashboard keeps working with security.csrf enabled.

  • Fixed: refresh-token reuse now revokes the session. A replayed / rotated-out refresh token invalidates the whole session instead of rejecting just the one request — containing token theft rather than letting the attacker retry.

  • Fixed: TOTP device deletion is scoped to its owner — you can no longer delete another user's TOTP device. OTP verification attempts are capped and recovery/OTP codes now use a CSPRNG.

  • Fixed (social): social login persists firstName / lastName / avatarUrl from the provider (fixes Apple's display name arriving only on the first authorization and never being saved).


2.7.6 — email/phone-first tenant picker helpers

  • Added (backend): UserService.getTenantsByEmail(email) / getTenantsByPhone(phone) — cross-tenant helpers for app-owned email/phone-first login pickers (especially ISOLATED). No public nest-auth HTTP route; call from your own controller. See Logging in under a tenant (ISOLATED).

2.7.5 — richer /auth/client-config for login/signup UIs

  • Added (backend): GET /auth/client-config now returns passwordless flags, OAuth public client/app ids (google / facebook / apple / github), customProviders, platformAccess.enabled, and accessTokenType — so login/signup screens can render without hardcoding. Secrets (clientSecret, appSecret, private keys, JWT secrets) are never included; public OAuth client/app ids are safe to expose.
  • Added (contracts): IClientConfig (+ related public config types) is the shared response shape; @ackplus/nest-auth-client re-exports it.
  • Docs: client + hooks-reference pages updated for the expanded config.

2.7.4 — no silent anonymous requests when no account is active

  • Fixed (client): with no active account, AccountManager.getAuthHeaders() / getAuthHeadersSync() / shouldSendCookies() / refresh() resolved to nothing silently, so every request through an attached axios/fetch went out anonymous and 401'd — while an AuthProvider fed a separate bootstrap client still reported the user signed in. Two token sources disagreeing with no signal.
  • Added: fallbackClient on AccountManagerConfig — the client to use when no account is active (typically your bootstrap client) — plus a public resolveActiveClient() (also on IAccountSwitcher) so your auth provider and your attached HTTP client resolve the same client and can't diverge.
  • Added: onNoActiveAccount({ method }) — fires when auth resolves to nothing, so you can log or redirect instead of silently 401ing. Defaults are unchanged (still non-fatal empty headers) when neither option is configured. See HTTP adapters and multi-account switching.
  • Behavior: resolution is boot-safe — the async resolvers await the account index before answering and the sync ones return the neutral default until it has loaded, so a persisted active account is never briefly impersonated by the fallbackClient.

2.7.3 — duplication-safe React contexts

  • Fixed (React): when @ackplus/nest-auth-react ends up installed twice (common in pnpm/monorepos when a peer-React version split double-installs it), each copy called createContext() and got its own context object — so <AuthProvider> from one copy populated one context while hooks imported from the other read a different, still-default one. isLoading stayed true forever and AuthGuard, RequirePermission / RequireRole, and the withRequirePermission / withRequireRole HOCs silently rendered a blank page for authenticated users.
  • Fixed: AuthContext and AccountSwitcherContext are now cross-realm singletons pinned on globalThis via Symbol.for(...), so every duplicate copy shares one context object. (Safe because React itself stays a single instance via peerDependency — only the context identity broke.)
  • Added: guards no longer fail completely silently while loading — when a guard renders nothing purely because auth is still loading and no loading UI was supplied, a one-time dev-only console.warn points at a duplicate install as the likely cause. Production builds stay silent.
  • No API changes — a drop-in fix. The real remedy is still to dedupe @ackplus/nest-auth-react to a single copy; this makes the app work even when you can't.

2.7.2 — multi-account storage GC + reset()

  • Fixed (client): AccountManager (header mode) no longer leaks orphaned per-account token namespaces in storage (<prefix>a_<uuid>_access_token / _refresh_token / _session). An interrupted add-account, an abandoned MFA/OTP pending client, or an index/storage desync previously stranded namespaces forever, because removeAccount can only target namespaces still in the index. On ready() the manager now reaps any namespace the index no longer references (opt out with reapOrphanStorageOnReady: false; a corrupt index never triggers reaping).
  • Added: AccountManager.reset() (also on CookieAccountManager, the IAccountSwitcher interface, and the React useAccountSwitcher()) — remove every account, revoke each session, and wipe all per-account storage. Use it for a "plain sign-in starts a fresh single-account session" rule.
  • Added: AccountManager.discardPendingClient(client) — clear an abandoned createPendingClient() / AccountMfaRequiredError pending client so its tokens don't linger.
  • Added: optional StorageAdapter.keys() (implemented by the local/session/memory adapters) that powers the GC. See multi-account switching.

2.7.1 — shared-axios refresh-deadlock fix

  • Fixed (client): sharing one axios for both AuthClient (createAxiosAdapter) and attachToAxios no longer deadlocks on an expired session. The boot verifySession 401 started a refresh whose refresh-token request re-entered the same interceptor and parked forever — the app hung on the splash screen and never cleared tokens or redirected to login. createAxiosAdapter now tags AuthClient's own requests and attachToAxios skips them; the new NEST_AUTH_ADAPTER_REQUEST export lets custom adapters opt out too.
  • Behavior: attachToAxios / attachToFetch now default-skip the auth endpoints (/auth/refresh-token, /auth/login, /auth/logout, /auth/logout-all) — they are never bearer-injected or refresh-retried. Do login/logout via the AuthClient / AccountManager methods; if you renamed those endpoints, pass the custom paths in skipPaths. See HTTP adapters.

2.7.0 — platform-user listing + passwordless login completion

  • Added (backend): UserService.getPlatformUsers(options?), getPlatformUsersAndCount(options?), and getPlatformUsersByRole(roleName, guard?) — list super-admins by the PlatformAccess marker without scanning tenant users (the list analog of getPlatformUserByEmail). Caller where / relations / pagination are honored.
  • Added (client + React): AuthClient.passwordlessLogin(dto) and useNestAuth().passwordlessLogin(dto) complete a passwordless sign-in — exchange the emailed/texted code for a session (the completion step for passwordlessSend), returning a normal auth response. New IPasswordlessLoginRequest ({ identifier, code, channel?, tenantId?, rememberMe? }); channel defaults to trying both email and SMS. Wraps POST /auth/login — no backend change.

2.6.0 — multi-account switcher DX

  • Added: attachToAxios / attachToFetch accept an AccountManager / AuthHeaderProvider, and the managers expose instance attachToAxios() / attachToFetch() — a shared axios/fetch follows the active account with no re-attach on switch.
  • Added: account naming — AccountSnapshot.tenantName, AccountMeta, addAccount(dto, { meta }), and setAccountMeta(accountId, meta) — so a same-email owner's accounts are distinguishable in the switcher.
  • Added (React): useAccountSwitcher().completeMfa(error, verifyDto) to finish an MFA-gated addAccount; commitAccount is now on IAccountSwitcher, and cookie mode surfaces AccountMfaRequiredError too.
  • Added (React): GuestGuard.allowWhenAddingAccount + a new <AddAccountGuard> for a Gmail-style "add another account" flow (render the login form while already signed in). See multi-account switching.

2.5.2 — tenant-less platform-user provisioning

  • Fixed (ISOLATED): provisioning a platform (super-admin) user no longer throws TENANT_ID_REQUIRED under TENANT_MODE=isolated. UserService.createUser / getUserByEmail require a tenantId there, which broke admin/platform-admin bootstrap on boot — platform admins are tenant-less.
  • Added: first-class UserService.createPlatformUser(data) and getPlatformUserByEmail(email) — tenant-less provisioning + lookup that work in every tenant mode. A platform user is identified by the PlatformAccess marker (the same row the login path enforces), so the lookup never returns a regular tenant account, and createPlatformUser establishes that marker atomically. See Platform admin portal.

Note: entries for 2.3.0 – 2.5.1 are not yet backfilled on this page; see each package's CHANGELOG.md / the GitHub releases for the interim versions.

2.2.0 — ISOLATED fixes, tenant lookup, cleanup

  • Fixed (ISOLATED tenant scoping): forgot-password, verify-forgot-password-otp, and phone login now scope the account lookup by the resolved tenantId. Previously, when the same email existed in multiple tenants, a reset/login could resolve the wrong account. Reset/verify tokens round-trip the tenant, so links land in the correct isolated tenant.
  • Added: GET /auth/tenants/lookup?slug= (public) — resolve a tenant slug → id so an ISOLATED login form can supply the right tenantId. Exact-slug only (no enumeration). See Logging in under a tenant (ISOLATED).
  • Fixed: the request-context middleware wildcard now uses the named form ({*splat}) on Express 5 / path-to-regexp v8, silencing the LegacyRouteConverter warning; Express 4 is unaffected.
  • Removed: dangling InitializeAdmin request/response DTOs and the IInitializeAdminRequest / IInitializeAdminResponse contracts (no route consumed them).
  • Docs: corrected the multi-tenancy page — ISOLATED is logical identity isolation in one database (same email = a separate account per tenant; switchTenant disabled; login needs a tenantId). The library does not switch data sources per tenant. New ISOLATED login recipe.

2.1.1 — client-config hooks

  • Added: AuthClient.getClientConfig() (+ the IClientConfig type) — fetch the backend's public config with no auth.
  • Added (React): useClientConfig() and useMultiAccountEnabled() — gate UI (e.g. the account switcher) on what the backend actually enables.
  • Docs: multi-account integrated into the config / client / React reference pages.

2.1.0 — Multi-account login & switching

Log into several accounts on one client and switch the active one (Gmail/Slack-style). Especially natural in ISOLATED mode, where the same email is a distinct account per tenant.

  • Backend: opt-in session.allowMultipleAccounts (default false), surfaced on GET /auth/client-config. Cookie mode gains per-account cookies + a non-httpOnly active-account selector and a GET /auth/accounts listing endpoint. The backend was already multi-session; switching is client-side.
  • Client SDK: AccountManager (header mode — one client per account, namespaced storage) and CookieAccountManager (cookie mode), behind a shared IAccountSwitcher interface.
  • React SDK: AccountSwitcherProvider (separate from AuthProvider) + useAccountSwitcher / useAccounts / useActiveAccount.
  • Recipe: Multi-account login & switching.

2.0.4 — @Public() works under a global guard

  • Fixed: NestAuthAuthGuard now honours @Public() (IS_PUBLIC_KEY) — previously a silent no-op. The documented global APP_GUARD + @Public() pattern works; the library's own public routes (/auth/login, /auth/signup, refresh, password reset, SSO callback, client-config) and the admin console are pre-marked, so a global guard no longer 401s login. See Guards.

2.0.3 — Postgres portability & optional peers

  • Fixed: nest_auth_trusted_devices.revokedAt used datetime, which Postgres rejects (the app couldn't boot). It now uses an inferred, portable type (boots on Postgres, MySQL, SQLite).
  • Fixed: the optional apple-auth peer is now lazy-loaded — apps that don't install it (or use native Apple sign-in) boot fine. (google-auth-library / fb were already lazy.)

2.0.2 — public-barrel exports

  • Fixed: Public / IS_PUBLIC_KEY and AuthExceptionFilter are now exported from the package barrel (they were defined but unreachable), plus a new @CurrentUser() decorator and a re-exported CurrentAdmin.
  • Added: a package exports map; corrected npm description/keywords.

2.0.1 — first stable v2

The first stable release of v2 (the 2.0.0-beta.* line preceded it). See the overview below and the v1 → v2 migration guide.


2.0.0 — What's new (v2 overview)

v2 is a major release: the same NestAuthModule.forRoot() wiring and flat config, but a substantially hardened core, several new capabilities, and complete docs. See the migration guide for breaking changes and how to upgrade.

New capabilities

  • Passwordless login — email/SMS OTP via passwordless: { enabled, allowSignUp }, with client/React passwordlessSend helpers and a POST /auth/passwordless/send endpoint.
  • Phone verificationPOST /auth/send-phone-verification and POST /auth/verify-phone, backed by a shared OTP flow service.
  • Platform admin — a first-class, cross-tenant super-admin (platformAccess: { enabled, validate }). See Platform admin portal.
  • Embedded admin dashboard — a full management UI served at /auth/admin (enable with adminConsole), backed by a documented REST API. No separate install.
  • GET /auth/me — a guarded current-user endpoint.

Reliability

  • Atomic user mutations — signup, admin/programmatic create, update, and delete each run in a single transaction. A failing hook, listener, or multi-step write rolls back completely: no partially-created or half-updated users. Lifecycle events fire only after commit.
  • Full lifecycle hooks — added user.beforeUpdate / afterUpdate / beforeDelete / afterDelete, and the transactional EntityManager is now passed to create/update/delete and onSignup / onLogin hooks so your sync code commits atomically with the user. See the hooks reference.
  • RBAC eventsRoleService and PermissionService now emit ROLE_* / PERMISSION_* created/updated/deleted events so role and permission changes are syncable. See the events reference.

Security hardening

  • Secrets hashed at rest — API-key secrets, MFA recovery codes, OTP codes, and trusted-device tokens are now stored hashed (and verified with constant-time comparisons). Trusted devices also support explicit revocation. Existing API keys must be regenerated — see the migration guide.
  • Refresh-token rotation + reuse detection — each refresh issues a new token and rejects a replayed/old one.
  • Configurable password hashing — bring-your-own password.hash / password.verify, or tune the built-in Argon2 (password.argon2).
  • OAuth hardening — Google requireVerifiedEmail / multi-audience native id-tokens, Apple native identityToken verification, and GitHub Enterprise endpoint overrides.

Developer experience

  • Complete API reference — a generated OpenAPI 3.0 spec rendered in the admin console and the docs site.
  • Real-database tests — the suite runs against a real database with no mocks.
  • Lighter & modern — dropped the moment dependency; requires Node ≥ 20 and pnpm ≥ 10.

Breaking changes

A focused set: token-TTL config renamed, a few hook/SDK method renames, and the API-key rehash. All of them — with before/after code — are in the v1 → v2 migration guide.