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.
- Upgrading from v1? Follow the v1 → v2 migration guide.
- Upgrading within v2 (e.g.
2.0.x→2.2.x)? It's additive/non-breaking — see Upgrading within v2. Bump every package to the same version.
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.secretis 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; setsession.jwt.validateSecretStrength: trueto make that a hard error too. -
Breaking: the
jwtlogin provider is now opt-in. It used to register automatically wheneversession.jwtexisted, and it trusts any token signed with your secret and mints a session for itssub— a privileged bypass that must be enabled deliberately withsession.jwt.enableLoginProvider: true. As defense-in-depth the auth guard now only acceptstype: 'access'tokens, so a refresh token presented as a Bearer is rejected. -
Breaking (admin console): a
secretKey/sessionSecretunder 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
sessionSecretis set, the admin session-signing key is now derived fromsecretKeyrather than being the rawsecretKey. Admin sessions minted by older versions are invalidated — admins re-login once after upgrade. -
Breaking (admin console): admin login is throttled by default (
429after ~5/min). Opt out withadminConsole.bruteForce.enabled: false. -
Breaking (admin console): the admin session cookie is now
SecureunlessNODE_ENVis explicitlydevelopmentortest(was:Secureonly whenNODE_ENV === 'production', so staging / unset / misconfigured prod shipped it in cleartext). Force it off withadminConsole.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 optionalallowedOriginscheck. No-op for bearer/header auth; required if you setsameSite: '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 sharedstorefor 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 withpassword.policy.checkBreached: true(Have I Been Pwned k-anonymity, fail-open). -
Added (opt-in): email-verification gating —
registration.requireVerifiedEmail: truehard-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.blockmode rejects with403 EMAIL_DOMAIN_NOT_ALLOWED;flagmode 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>/signupis refused and further admins must be created by a signed-in admin from the dashboard, so a leakedsecretKeycan't mint unlimited super-admins. Restore the legacy shared-key path withadminConsole.allowPublicSignupAfterFirstAdmin: true. -
Hardened (admin console): revocable admin sessions — a dashboard password change now bumps
tokenVersionand 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 realContent-Security-Policywithframe-ancestors 'none'(a<meta>CSP can't set that, so a logged-in admin was frameable),X-Content-Type-Options: nosniff, andReferrer-Policy: no-referrer— and the injectedwindow.__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 withsecurity.csrfenabled. -
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/avatarUrlfrom 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-confignow returns passwordless flags, OAuth public client/app ids (google/facebook/apple/github),customProviders,platformAccess.enabled, andaccessTokenType— 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-clientre-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 anAuthProviderfed a separate bootstrap client still reported the user signed in. Two token sources disagreeing with no signal. - Added:
fallbackClientonAccountManagerConfig— the client to use when no account is active (typically your bootstrap client) — plus a publicresolveActiveClient()(also onIAccountSwitcher) 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-reactends up installed twice (common in pnpm/monorepos when a peer-React version split double-installs it), each copy calledcreateContext()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.isLoadingstayedtrueforever andAuthGuard,RequirePermission/RequireRole, and thewithRequirePermission/withRequireRoleHOCs silently rendered a blank page for authenticated users. - Fixed:
AuthContextandAccountSwitcherContextare now cross-realm singletons pinned onglobalThisviaSymbol.for(...), so every duplicate copy shares one context object. (Safe because React itself stays a single instance viapeerDependency— 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.warnpoints 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-reactto 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, becauseremoveAccountcan only target namespaces still in the index. Onready()the manager now reaps any namespace the index no longer references (opt out withreapOrphanStorageOnReady: false; a corrupt index never triggers reaping). - Added:
AccountManager.reset()(also onCookieAccountManager, theIAccountSwitcherinterface, and the ReactuseAccountSwitcher()) — 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 abandonedcreatePendingClient()/AccountMfaRequiredErrorpending 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) andattachToAxiosno longer deadlocks on an expired session. The bootverifySession401 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.createAxiosAdapternow tagsAuthClient's own requests andattachToAxiosskips them; the newNEST_AUTH_ADAPTER_REQUESTexport lets custom adapters opt out too. - Behavior:
attachToAxios/attachToFetchnow 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 theAuthClient/AccountManagermethods; if you renamed those endpoints, pass the custom paths inskipPaths. See HTTP adapters.
2.7.0 — platform-user listing + passwordless login completion
- Added (backend):
UserService.getPlatformUsers(options?),getPlatformUsersAndCount(options?), andgetPlatformUsersByRole(roleName, guard?)— list super-admins by thePlatformAccessmarker without scanning tenant users (the list analog ofgetPlatformUserByEmail). Callerwhere/relations/ pagination are honored. - Added (client + React):
AuthClient.passwordlessLogin(dto)anduseNestAuth().passwordlessLogin(dto)complete a passwordless sign-in — exchange the emailed/texted code for a session (the completion step forpasswordlessSend), returning a normal auth response. NewIPasswordlessLoginRequest({ identifier, code, channel?, tenantId?, rememberMe? });channeldefaults to trying both email and SMS. WrapsPOST /auth/login— no backend change.
2.6.0 — multi-account switcher DX
- Added:
attachToAxios/attachToFetchaccept anAccountManager/AuthHeaderProvider, and the managers expose instanceattachToAxios()/attachToFetch()— a shared axios/fetch follows the active account with no re-attach on switch. - Added: account naming —
AccountSnapshot.tenantName,AccountMeta,addAccount(dto, { meta }), andsetAccountMeta(accountId, meta)— so a same-email owner's accounts are distinguishable in the switcher. - Added (React):
useAccountSwitcher().completeMfa(error, verifyDto)to finish an MFA-gatedaddAccount;commitAccountis now onIAccountSwitcher, and cookie mode surfacesAccountMfaRequiredErrortoo. - 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_REQUIREDunderTENANT_MODE=isolated.UserService.createUser/getUserByEmailrequire atenantIdthere, which broke admin/platform-admin bootstrap on boot — platform admins are tenant-less. - Added: first-class
UserService.createPlatformUser(data)andgetPlatformUserByEmail(email)— tenant-less provisioning + lookup that work in every tenant mode. A platform user is identified by thePlatformAccessmarker (the same row the login path enforces), so the lookup never returns a regular tenant account, andcreatePlatformUserestablishes 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 resolvedtenantId. 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 righttenantId. 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 theLegacyRouteConverterwarning; Express 4 is unaffected. - Removed: dangling
InitializeAdminrequest/response DTOs and theIInitializeAdminRequest/IInitializeAdminResponsecontracts (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;
switchTenantdisabled; login needs atenantId). The library does not switch data sources per tenant. New ISOLATED login recipe.
2.1.1 — client-config hooks
- Added:
AuthClient.getClientConfig()(+ theIClientConfigtype) — fetch the backend's public config with no auth. - Added (React):
useClientConfig()anduseMultiAccountEnabled()— 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(defaultfalse), surfaced onGET /auth/client-config. Cookie mode gains per-account cookies + a non-httpOnly active-account selector and aGET /auth/accountslisting endpoint. The backend was already multi-session; switching is client-side. - Client SDK:
AccountManager(header mode — one client per account, namespaced storage) andCookieAccountManager(cookie mode), behind a sharedIAccountSwitcherinterface. - React SDK:
AccountSwitcherProvider(separate fromAuthProvider) +useAccountSwitcher/useAccounts/useActiveAccount. - Recipe: Multi-account login & switching.
2.0.4 — @Public() works under a global guard
- Fixed:
NestAuthAuthGuardnow honours@Public()(IS_PUBLIC_KEY) — previously a silent no-op. The documented globalAPP_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.revokedAtuseddatetime, which Postgres rejects (the app couldn't boot). It now uses an inferred, portable type (boots on Postgres, MySQL, SQLite). - Fixed: the optional
apple-authpeer is now lazy-loaded — apps that don't install it (or use native Apple sign-in) boot fine. (google-auth-library/fbwere already lazy.)
2.0.2 — public-barrel exports
- Fixed:
Public/IS_PUBLIC_KEYandAuthExceptionFilterare now exported from the package barrel (they were defined but unreachable), plus a new@CurrentUser()decorator and a re-exportedCurrentAdmin. - Added: a package
exportsmap; 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/ReactpasswordlessSendhelpers and aPOST /auth/passwordless/sendendpoint. - Phone verification —
POST /auth/send-phone-verificationandPOST /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 withadminConsole), 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 transactionalEntityManageris now passed to create/update/delete andonSignup/onLoginhooks so your sync code commits atomically with the user. See the hooks reference. - RBAC events —
RoleServiceandPermissionServicenow emitROLE_*/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
momentdependency; 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.