72 lines
5.4 KiB
Markdown
72 lines
5.4 KiB
Markdown
# Authentication security guidance
|
|
|
|
## Required production controls
|
|
|
|
1. Use a cryptographically random authentication HMAC secret of at least 32 characters.
|
|
2. Use `SqlAuthStore` or another durable shared `AuthStore`; `MemoryAuthStore` is for development and tests.
|
|
3. Protect TOTP seeds and OAuth tokens with `createAuthSecretProtector()` and a rotated `@wrnexus/encryption` keyring.
|
|
4. Queue verification delivery and never log OTP codes, raw one-time tokens, reset URLs, or provider secrets.
|
|
5. Rate-limit registration, login, verification, OTP, reset, magic-link, invitation, passkey, and impersonation endpoints.
|
|
6. Accept CAPTCHA success only from a server-side verifier such as `captchaGuard()`.
|
|
7. Keep package CSRF verification enabled for cookie-authenticated unsafe requests unless an equivalent gateway control replaces it.
|
|
8. Validate every post-authentication redirect as same-origin.
|
|
9. Use HTTPS, `Secure` and `HttpOnly` session cookies, an appropriate `SameSite` policy, strict security headers, and trusted-proxy configuration.
|
|
10. Persist security events in a durable, access-controlled audit sink.
|
|
11. Require a reason and explicit authorization policy for impersonation and show a persistent banner while it is active.
|
|
12. Require recent or step-up authentication before especially sensitive account-management actions when the surrounding product policy demands it.
|
|
|
|
## Passwords and lockout
|
|
|
|
Passwords use the framework password API. Keep the default 12-character minimum or raise it, and configure a breach provider in production. Browser validation is only a usability layer; server validation and engine policy remain authoritative.
|
|
|
|
Temporary failed-login locks preserve the previous account state. A valid password reset clears only a failed-login lock; it does not reactivate an administratively locked, disabled, or deleted account.
|
|
|
|
## Tokens and OTP
|
|
|
|
Reset, verification, invitation, magic-link, and MFA transaction tokens are HMAC-hashed at rest, expire, and are one-use. OTP challenges are bound to verification, login, or MFA purpose and have attempt limits. Email/SMS MFA can use only verified linked identities.
|
|
|
|
Durable custom stores should consume one-time credentials atomically, preferably in the same transaction as the protected state change. The generic `AuthStore` interface exposes read/update operations and cannot by itself guarantee compare-and-set behavior across concurrent processes.
|
|
|
|
Public reset, magic-link, passwordless-OTP, and verification-request endpoints return generic responses to reduce account enumeration. Rate limiting and delivery-abuse controls are still required.
|
|
|
|
For login forms, pair `captchaGuard({ verifiedForMs })` with
|
|
`<Captcha resetOnError="false" />` so a successful human check can survive an
|
|
incorrect-password retry. The grant remains session-bound and action-bound;
|
|
authentication rate limits, risk blocking, and account lockout remain required.
|
|
|
|
## Account recovery
|
|
|
|
Disabled, deleted, and administratively locked accounts cannot complete verification or password recovery with previously issued tokens. Password reset revokes active sessions. Recovery-code regeneration invalidates earlier unused codes.
|
|
|
|
## Sessions and trusted devices
|
|
|
|
The WRNexusJS session container is regenerated when an auth session is established. Auth sessions have idle and absolute expiry. Trusted-device values should be opaque, stable identifiers generated by the application—not high-entropy browser fingerprint profiles containing unnecessary personal data.
|
|
|
|
## TOTP and recovery codes
|
|
|
|
TOTP validation rejects reused counters. Encrypt TOTP seeds with `secretProtector`. Recovery codes are displayed once and stored only as hashes.
|
|
|
|
## Passkeys
|
|
|
|
The browser runtime does not perform cryptographic verification. The configured `PasskeyProvider` must verify the challenge, RP ID, exact origin, signature, user presence or verification, counter, and credential ownership. Require HTTPS outside localhost.
|
|
|
|
The default challenge store is memory-only. Use a shared Redis, SQL, or equivalent `PasskeyChallengeStore` when requests can reach more than one process. Passkeys are implemented as strong passwordless sign-in; they are not currently a selectable method in the second-step MFA challenge component.
|
|
|
|
## OAuth
|
|
|
|
Use PKCE, state, nonce, and callback validation from `@wrnexus/oauth`. Encrypt provider tokens before durable storage. Never auto-link an OAuth account solely from an unverified provider email.
|
|
|
|
## Database integrity
|
|
|
|
Run both package migrations in order. Production schemas should preserve the included unique constraints for normalized identities, passkey credential IDs, trusted-device fingerprints, token hashes, and OAuth provider account IDs.
|
|
|
|
Registration and one-time credential state changes should run in transactions when a custom store supports them. This prevents partial writes and concurrent replay beyond what a generic multi-operation store can guarantee.
|
|
|
|
## Impersonation
|
|
|
|
`authorizeImpersonation` defaults to deny. Restrict it to named roles, require a reason, preserve the actor session, display `ImpersonationBanner`, and notify the organization when policy requires it. Apply a separate rule before allowing an actor to impersonate a more privileged target.
|
|
|
|
## Delivered links
|
|
|
|
Use `AuthEngineOptions.tokenUrl` to map one-time credentials to application pages. Treat the callback as trusted server configuration, use HTTPS production origins, and do not log raw tokens or generated URLs.
|