Context
This is the Better Auth configuration for the organizational realm. The member realm is configured separately incore (ADR-010), and keeping the two configurations in different modules is what
makes realm separation visible rather than remembered.
The design position worth stating: we support passwords because B2B customers expect them, not
because they are good. Everything around them is therefore hardened — breach-list checking, no
composition rules that push users toward Password1!, and MFA (FS-IDN-0005) on top for anyone
holding a sensitive permission. Magic link and passkey exist so a tenant that wants to avoid
passwords entirely can.
Scope (normative)
- Password authentication with modern hashing and breach-list checking.
- Magic link over email, single-use and short-lived.
- Passkeys (WebAuthn) as a first-class method.
- Email verification and password reset flows.
identity.auth_accountslinking a user to each method they have enrolled.- Rate limiting and lockout on every credential path.
Non-scope (normative)
- Social login and enterprise SSO — FS-IDN-0008.
- MFA — FS-IDN-0005. A second factor is a separate concern from a first one.
- Session issuance and lifetime — FS-IDN-0004.
- Member authentication, which lives in
core.
Behaviour (normative)
- A user may hold several methods at once: a password, a magic link and two passkeys. Removing the last method is rejected — it would lock the user out of their own account.
- Passwords are hashed with a memory-hard algorithm at parameters recorded in the spec and reviewed annually. Plain or reversibly encrypted storage is FORBIDDEN, and so is logging a password anywhere at any level.
- No composition rules. A minimum length and a breach-list check, nothing else. Rules that demand a symbol and a digit produce predictable passwords and a reuse habit; a breach check rejects the passwords that are actually compromised.
- Magic links and reset tokens are single-use, expire in 15 minutes, are stored hashed, and are invalidated when any other one is issued for the same user.
- Sign-in failures return an identical response and timing whether the email exists or not. Distinguishing them turns the endpoint into an account-enumeration oracle.
- Rate limiting is per identifier and per IP, with progressive lockout. A lockout notifies the user by email, because a lockout they did not cause is the signal that someone is trying.
- A password change or reset revokes every other session of that user and notifies them.
- Email verification is required before a user carries any role beyond
member. - Passkeys follow WebAuthn; a user may enrol several and name each, because a passkey bound to a lost laptop must be removable from a phone.
- FORBIDDEN: any credential in a log, an event payload, an error message or a URL.
Data (normative)
API (normative)
Events (normative)
None on the outbox. Authentication is recorded incore.audit_log with a typed actor; a domain
event per sign-in would flood every consumer for no benefit.
Acceptance criteria (normative)
- Sign-in with an unknown email and with a wrong password are indistinguishable in body, status and timing — verified with a timing test, not by inspection.
- A password on a breach list is rejected at signup and at change.
- A magic link cannot be used twice, and expires at 15 minutes.
- Issuing a second magic link invalidates the first.
- A password reset revokes every other session and sends a notification.
- Removing the last authentication method is rejected.
- Progressive lockout triggers on repeated failures and notifies the user.
- A passkey enrolled on one device can be removed from another.
- Negative: no credential, token or reset link appears in any log, event, error message or URL — verified by a scan over a seeded request set.
Execution
Single slice, synchronous command. Better Auth configured for the organizational realm inbackend/api; magic links and notifications are delivered through messaging by event, never by
calling a provider from here.