Skip to content
AsterDrive Developer DocsDeveloper

Authentication Flow State-Machine Contract

This contract defines the shared lifecycle boundary for primary login, second factors, account recovery, and sessions. MFA, Passkey, external-auth, contact-verification, invitation, and session modules continue to own their typed security payloads. The shared state machine owns only identity, state, expiry, attempt budgets, single-use behavior, and concurrency rules.

HTTP route
-> typed auth command
-> auth domain transition guard
-> owning service transaction
-> repository conditional update / cache atomic take
-> commit
-> cookie, redirect, mail, and audit side effects
  • src/services/auth/flow/ defines AuthFlowKind, AuthFlowState, commands, snapshots, and transition rules.
  • MFA, external-auth, local recovery, Passkey, and session services retain product guards, runtime policy checks, and side-effect ordering.
  • Repositories only perform atomic conditional updates. rows_affected == 0 or cache take == None is a no-result outcome that may represent conflict, replay, expiry, or cache eviction; services inspect authoritative fields and map the final state, while repositories do not choose UI behavior.
  • Routes preserve the existing API envelope, cookie, and redirect contracts.
FlowPayload ownerIdentityAtomic advanceTerminal / cleanup
Password primarylocal auth servicerequest-localpassword verification, then MFA/password-change/session resultrequest completion
MFA loginmfa_login_flowsmfa-login:<id>transaction plus conditional attempt/consumeconsumed/expired; runtime cleanup
Passkey login/registrationtyped cache envelopepublic flow UUIDcache atomic takeconsumed/TTL eviction
External loginexternal_auth_login_flowsexternal-login:<id>state plus browser-binding conditional consumeconsumed/expired; runtime cleanup
External email recoveryexternal_auth_email_verification_flowsexternal-recovery:<id>conditional email request/consumeconsumed/expired; runtime cleanup
Registration/password reset/email changecontact_verification_tokenscontact-verification:<id>transaction plus purpose-scoped single-use tokenconsumed/expired cleanup
Invitationuser_invitationsinvitation:<id>status-scoped conditional updateaccepted/expired/revoked
Sessionauth_sessionssession UUIDconditional refresh-JTI rotationrevoked/expired cleanup

Identities use only database primary keys or public flow UUIDs. The request-local identity of a password primary is an explicit exception: it ends with the request and must not enter cross-request shared snapshots, logs, or errors. Raw tokens, provider state, browser bindings, verification codes, refresh JTIs, and their hashes never enter shared snapshots, logs, or errors.

  • Password primary may enter SecondFactorPending, PasswordChangeRequired, or Authenticated.
  • Passkey and external primary flows advance through FirstFactorPending -> Processing, then MFA or authenticated. Local password policy does not block either factor.
  • MFA advances only from SecondFactorPending to password-change or authenticated.
  • Recovery advances through RecoveryPending -> Processing -> Completed.
  • Failed, Expired, Cancelled, Consumed, Completed, and Authenticated are terminal.
  • Revision conflicts are checked before terminal state. Except for explicit Expire, expiry is checked before cancel and ordinary transitions.
  • Failure commands atomically increment attempts and enter Failed at the budget. Saturating arithmetic prevents counter wraparound.
  • Every cross-request advance rereads runtime auth policy. A UI snapshot captured at flow creation is not authorization evidence.
  • Password-first MFA rechecks password-login policy during exchange; external-first MFA is unaffected by that switch.
  • The session row is persisted in a transaction before the route emits cookies. Transaction failure emits no session cookie.
  • Mail outbox, audit, and cache invalidation have an explicit pre-commit or post-commit position. Failures are not silently discarded.
  • Frontend AuthUiFlow is the single frontend/UI projection of backend state. The URL adapter restores only an expiring flow reference and bounds TTL, methods, and local return paths.
  • A generation-aware coordinator combines auth check and provider loading. An older generation, unmounted promise, or slower response never updates the active page.
  • Domain: every allowed transition, cross-context rejection, terminal replay, revision conflict, attempt boundaries, saturation, and expiry/cancel ordering.
  • Repository/integration: single conditional consume, concurrent loser, no session on failure, runtime policy changes, and expired cleanup.
  • Passkey cache: flow isolation, single take, TTL envelope, and registration/login kind isolation.
  • Frontend: one top-level flow, URL recovery precedence, partial provider failure, stale generations, hostile return paths, TTL limits, unknown/duplicate methods, and query cleanup.

The typed tables already retain their security payload and authoritative lifecycle through consumed_at, expires_at, attempt counts, or status. Shared snapshots derive from those fields instead of creating a second state column. A typed table gains a revision only when a real multi-request CAS cannot be represented by its existing conditional fields. There is no universal auth JSON table.