WEB / WEB-14.S01
Account lifecycle and password verification
Account records, verified contacts, password verifiers, and sessions are separate state. Track which control changes which state from registration through disablement and closure.
1. Core · Mechanism
Read every scenario without motion
Registration
- Registration request
Contact and password are aliases only; the account does not yet exist.
- Browser / Duru
- Registration alias ready
- Application
- No request
- Account / verifier records
- No account
- Registration input arrived
Input policy is supplied; trusted identifier uniqueness is checked.
- Browser / Duru
- Waiting for registration receipt
- Application
- Received registration claim
- Account / verifier records
- No account
- Pending account and verifier
Local creation: contact pending, verifier alias stored, no session.
- Browser / Duru
- Waiting for registration receipt
- Application
- Pending account created
- Account / verifier records
- subject-duru: pending-contact; verifier current alias
- Generic receipt at the Browser
The Browser received the registration receipt; the account is still pending-contact. This example omits mail and token delivery and assumes the verification alias is now available in the Browser. The Browser can prepare the verify-contact request; no session has been issued.
- Browser / Duru
- Verification alias ready; no session
- Application
- Pending contact verification
- Account / verifier records
- subject-duru: pending-contact; verifier current alias
- Verification request arrived
The verification alias prepared in the Browser reached the Application. The Application will check its subject-duru and purpose=verify-contact binding, unused state and lifetime; arrival alone does not activate the account.
- Browser / Duru
- Waiting for verification result
- Application
- Verification alias received
- Account / verifier records
- subject-duru: pending-contact; token unused
- Contact-binding commit
Supplied match/subject/purpose/lifetime/unused are true; token consumption and activation commit locally.
- Browser / Duru
- Waiting for verification result
- Application
- Contact verified; no session issued
- Account / verifier records
- subject-duru: active; token consumed
- Active-account result arrived
Verification activates account state; normal login remains separate.
- Browser / Duru
- Contact verified; normal login available
- Application
- Verification complete
- Account / verifier records
- subject-duru: active; token consumed
Active verification and rehash
- Account and credential start
Account status and old-verifier metadata are distinct; the candidate is an alias only.
- Browser / Duru
- Login candidate alias ready
- Application
- No request
- Account / verifier records
- active; old verifier alias
- Login request arrived
Trusted account and throttle state will be evaluated locally.
- Browser / Duru
- Waiting for login result
- Application
- Candidate received; no context
- Account / verifier records
- active; old verifier alias
- Throttle and verifier gate
With attemptAllowed=true, verification is supplied as called in this example, with passwordMatches=true. accountStatus=active is checked separately. Account eligibility does not mean the rehash has committed yet.
- Browser / Duru
- Waiting for login result
- Application
- Context eligible; upgrade planned
- Account / verifier records
- active; old verifier alias
- Conditional verifier update
The active branch upgrades only the expected generation; the disabled branch leaves the verifier unchanged.
- Browser / Duru
- Waiting for login result
- Application
- Context eligible
- Account / verifier records
- active; current verifier alias
- Login result at the Browser
The Browser received a successful login result. Verification and account policy passed for this active account, and the expected generation’s verifier upgrade completed. The scene does not show a session cookie in this response or permission for every resource action.
- Browser / Duru
- Authenticated context eligible
- Application
- Accepted context decision
- Account / verifier records
- active; current verifier alias
Account disabled
- Account and credential start
Account status and old-verifier metadata are distinct; the candidate is an alias only.
- Browser / Duru
- Login candidate alias ready
- Application
- No request
- Account / verifier records
- disabled; old verifier alias
- Login request arrived
Trusted account and throttle state will be evaluated locally.
- Browser / Duru
- Waiting for login result
- Application
- Candidate received; no context
- Account / verifier records
- disabled; old verifier alias
- Throttle and verifier gate
Despite attemptAllowed=true and passwordMatches=true, accountStatus=disabled. Failed account eligibility withholds new authenticated context; successful password verification does not activate the account.
- Browser / Duru
- Waiting for login result
- Application
- Denied: account disabled
- Account / verifier records
- disabled; old verifier alias
- Verifier retained
The disabled account cannot receive an authenticated context. Its existing verifier is retained; this branch performs no rehash or upgrade.
- Browser / Duru
- Waiting for login result
- Application
- Context denied
- Account / verifier records
- disabled; old verifier alias
- Login result at the Browser
The Browser received a generic authentication failure. Internally the disabled account caused rejection; contextIssued=false and rehashCommitted=false remain. This response does not prove invalidation of existing sessions at every node.
- Browser / Duru
- Authentication could not be completed
- Application
- Denied: account disabled
- Account / verifier records
- disabled; old verifier alias
Attempt blocked
- Account and credential start
Account status and old-verifier metadata are distinct; the candidate is an alias only.
- Browser / Duru
- Login candidate alias ready
- Application
- No request
- Account / verifier records
- active; old verifier alias
- Login request arrived
Trusted account and throttle state will be evaluated locally.
- Browser / Duru
- Waiting for login result
- Application
- Candidate received; no context
- Account / verifier records
- active; old verifier alias
- Throttle and verifier gate
With attemptAllowed=false, verification is not called: verifyCalled=false and passwordMatches=null. This is not a wrong-password result. This attempt produces neither a new context nor verifier migration.
- Browser / Duru
- Waiting for login result
- Application
- Denied: attempt blocked
- Account / verifier records
- active; old verifier alias
- Login result at the Browser
The Browser received a generic authentication failure. Internally the attempt stopped before verification, leaving the password outcome unknown and account/verifier records unchanged. An active account does not override this throttle decision.
- Browser / Duru
- Authentication could not be completed
- Application
- Denied: attempt blocked
- Account / verifier records
- active; old verifier alias
ACTORS · STATE · DATA FLOW
An account is not one loggedIn boolean
Atlas stores subject-duru with a login identifier, contact verification, account status, credential generation, and authenticator associations. A session is not this account record. A new account can exist in pending-contact state without being eligible for protected login; a disabled account can still have a matching password verifier.
The fixture’s state names are application-owned: pending-contact, active, disabled, and closed. They are not required OWASP or NIST database enums. Administrative disablement, temporary throttling, and closure have different reasons; none automatically specifies another’s duration or recovery path.
Registration separates an account claim from contact control
Duru supplies duru.reader and a contact alias on the registration form. The Application checks uniqueness and product eligibility at the trusted boundary and uses suitable adaptive verification from WEB-14.01 for passwords. CandidateAllowed and identifierAvailable are supplied true; the fixture contains neither real registration data nor a password.
Atlas requires verified contact before activating this example account. A verification token is bound to purpose=verify-contact and subject-duru; tokenMatched, unused, and lifetimeValid jointly allow contactVerified=true and pending-contact→active. Mail transport is outside the scene, which assumes the prepared alias is available.
Contact verification supplies evidence about control of a communication endpoint. It does not prove a real-world identity, MFA assurance, or cross-tenant permission. Public registration and verification messages need not expose every internal account-record detail.
Password policy at enrollment/change; verification at login
An established password library verifies the candidate using the record’s algorithm, salt, and cost metadata without decrypting a hash. Salt is not secret. Even a verifier-only backup is sensitive material; database/backup access, credential logging, and metadata migration need joint assessment.
Selection/change applies a compromised/common-value blocklist, length, and supported-input policy. Requirements in the SP 800-63B-4 scope should not become arbitrary complexity or forced periodic changes. Document the product’s assurance target and the library’s byte/Unicode/truncation behavior; this fixture prescribes no production length or cost value.
Changing credential policy differs from accidentally disabling an existing account. An old verifier can verify successfully and then be rehashed under an updated policy. Password change/reset and rehash are different lifecycle events.
Account gates and safe rehash after verification succeeds
Normal login begins with an active account and an allowed attempt. Supplied passwordMatches=true permits subject resolution. If needsRehash=true, an established library can create a new verifier with a new salt/current cost policy while the candidate is available in the controlled verification path. The fixture changes verifier-old-alias to verifier-current-alias while retaining the same subject.
Rehashing can race with credential-generation changes. A stale login must not overwrite a newly reset verifier with its old candidate. Use account/credential-version checks for conditional updates to the expected generation and reassess conflicts; this is a high-level consistency rule.
The disabled branch has passwordMatches=true but accountAllowed=false, so no new authenticated context is granted. Propagation of existing-session invalidation to each node requires separate design; a verifier match does not establish that sessions were invalidated.
Throttling state and public failures are different surfaces
Throttling governs whether an attempt is allowed; a source-IP-only counter does not fully express account-level behavior. Atlas rejects a blocked attempt before calling the KDF. Distributed limit state, contention, reset conditions, and legitimate-user lockout depend on implementation; no count is presented as a universal retry policy.
Absent accounts, wrong passwords, pending contact, and disabled accounts can share one generic public authentication-failure body. Internal reasons remain separate for troubleshooting/control verification. Matching bodies alone prove neither equal HTTP status, size, timing, redirects, nor registration/reset behavior.
Review the owned public-response matrix against internal state to explain exposed detail. This lesson supplies no request workflow for discovering accounts and makes no constant-time guarantee without actual latency measurements.
Disablement, closure, and recovery are not interchangeable
When account eligibility ends, authenticators and access relationships need controlled invalidation. Retention/deletion after closure depends on the product and its obligations; a closed login gate does not imply deletion of every audit record or backup.
Recovery, clearing a temporary throttle, and changing a password from an active session use different authorization gates. WEB-14.S02 develops recovery and sensitive changes. A contactVerified flag at enrollment does not permit every future factor replacement unconditionally through email.
2. Red · Trust & Impact
Read every scenario without motion
Disabled account
- Account and attempt state were separated
The account is disabled with attemptAllowed=true. A credential match cannot automatically make this lifecycle state active.
- Account/attempt policy
- case=disabled
- Credential verification
- No verify outcome
- Context issuer
- No context issued
- The throttle gate was evaluated
The allowed attempt proceeds with verifyCalled=true; passing the throttle gate is not an account-eligibility decision.
- Account/attempt policy
- case=disabled
- Credential verification
- Verify called
- Context issuer
- No context issued
- The password outcome remained in scope
passwordMatches=true is supplied. This predicate alone is insufficient to issue authenticated context for a disabled account.
- Account/attempt policy
- case=disabled
- Credential verification
- passwordMatches=true
- Context issuer
- No context issued
- Eligibility and generation controls were preserved
The nonactive account has contextIssued=false and rehashCommitted=false. A migration need does not open the lifecycle gate.
- Account/attempt policy
- case=disabled
- Credential verification
- Rehash committed=false
- Context issuer
- Context issued=false
- Context issuance was withheld
No new authenticated context is established for the subject. Resource permission remains a separate downstream condition; no action grant is generated from an unissued context.
- Account/attempt policy
- case=disabled
- Credential verification
- No credential migration
- Context issuer
- No authenticated context
- The rejection reason remained accurate
This case separates a known password match from disabled lifecycle state; a generic public message does not erase that distinction in the internal audit.
- Account/attempt policy
- case=disabled
- Credential verification
- Known: disabled policy denial
- Context issuer
- Context issued=false
Blocked attempt
- Account and attempt state were separated
The account is active with attemptAllowed=false. Being active does not mean every attempt can reach the verifier or session issuer.
- Account/attempt policy
- case=throttled
- Credential verification
- No verify outcome
- Context issuer
- No context issued
- The throttle gate was evaluated
A blocked attempt keeps verifyCalled=false and passwordMatches=null. No call is not relabelled as a wrong-password result.
- Account/attempt policy
- case=throttled
- Credential verification
- Verify not called
- Context issuer
- No context issued
- The password outcome remained in scope
passwordMatches is unknown because the verifier was not called. needsRehash=true metadata is not substituted for successful verification.
- Account/attempt policy
- case=throttled
- Credential verification
- passwordMatches=null
- Context issuer
- No context issued
- Eligibility and generation controls were preserved
The blocked attempt has contextIssued=false and rehashCommitted=false. Metadata updates are not an alternate route to authenticated context.
- Account/attempt policy
- case=throttled
- Credential verification
- Rehash committed=false
- Context issuer
- Context issued=false
- Context issuance was withheld
No new authenticated context is established for the subject. Resource permission remains a separate downstream condition; no action grant is generated from an unissued context.
- Account/attempt policy
- case=throttled
- Credential verification
- No credential migration
- Context issuer
- No authenticated context
- The rejection reason remained accurate
This case separates a blocked attempt from an unknown password outcome. A null predicate is not presented as a credential mismatch or attacker attribution.
- Account/attempt policy
- case=throttled
- Credential verification
- Known: blocked; verify not called
- Context issuer
- Context issued=false
ASSUMPTION · BOUNDARY · CONTROL
When credential matching is confused with account eligibility
Normal login combines password verification and account policy in one trusted decision chain. Passing a match boolean directly to a session issuer can skip pending, disabled, or closed state. The effect is a new authenticated context for an account whose access should end; resource authorization remains another boundary.
Explicit eligibility and credential-generation checks block this failure. Design consistency between concurrent disablement and context issuance, and make cached-status freshness assumptions visible. Neither an active UI label nor client-supplied status provides authority.
What does a public response discrepancy expose?
A registration/login response distinguishing “account absent” from “wrong password” adds account-existence information to the public contract. The prerequisite is an endpoint exposing that distinction; an observation does not supply credentials or access permission.
Generic messages repair one layer. Timing and redirect differences may remain; conversely, body differences can reflect locale or product flow rather than account discovery. Inspect the owned response matrix and internal reasons under controlled conditions; selector output does not establish live endpoint efficacy.
3. Blue · Secure development & defense
Read every scenario without motion
Contact verified
- The registration state was selected
Registration starts with initialStatus=pending-contact. An available identifier and allowed candidate do not establish a new authenticated session.
- Account observation
- Pending contact
- Transition/response review
- Transition not examined
- Scope conclusion
- No session evidence
- The contact-proof purpose was read
The purpose is verify-contact; subjectMatches, tokenMatches, unused and lifetimeValid are supplied as true. The contact alias is neither an actual message address nor a usable token.
- Account observation
- verify-contact scope
- Transition/response review
- Contact predicates true
- Scope conclusion
- No session evidence
- The terminal proof state was read
afterTokenState=consumed records one-time use of contact proof in this transition. Contact verification and password verification are different proof types.
- Account observation
- verify-contact scope
- Transition/response review
- Proof consumed
- Scope conclusion
- No session evidence
- Account transition and session were separated
afterStatus becomes active while sessionIssued=false remains. A contact check changing account eligibility does not mean an ordinary authenticated session was issued.
- Account observation
- Account active
- Transition/response review
- Proof consumed
- Scope conclusion
- Session issued=false
- No delivery or KDF measurement was added
The record contains no mail delivery, user inbox observation or actual Argon2id verification. Alias metadata is not converted into production cost or latency values.
- Account observation
- Account active
- Transition/response review
- Transition known
- Scope conclusion
- Delivery/KDF outcome unknown
- The registration conclusion was preserved
For this fixture, contact proof consumption and an active account are known, with no session issuance. Login and resource authorization are evaluated independently of this limited-purpose transition.
- Account observation
- Known: account active
- Transition/response review
- Known: proof consumed
- Scope conclusion
- No ordinary session
Public message
- The failure matrix was selected
Review uses publicFailures for absent-account, wrong-password, pending-contact and disabled-account. Internal reasons and public responses serve different purposes.
- Account observation
- 4 failure rows
- Transition/response review
- Public bodies not compared
- Scope conclusion
- No timing evidence
- Body equality was compared
All four rows use publicBody Authentication could not be completed. This shows equality of body text; equality of HTTP status or redirects is not yet supplied.
- Account observation
- 4 failure rows
- Transition/response review
- Public body equal
- Scope conclusion
- Status/redirect unknown
- The internal-reason distinction was preserved
Absent, wrong, pending and disabled remain separate reasons despite their shared public message. Generic text does not require losing the decision source in the audit.
- Account observation
- 4 distinct internal reasons
- Transition/response review
- Same public body
- Scope conclusion
- Status/redirect unknown
- Context issuance was read separately
All four failure rows have contextIssued=false. A generic public message is not an assumption concealing internal issuance; the field is checked separately.
- Account observation
- 4 failure rows
- Transition/response review
- Context issued=false ×4
- Scope conclusion
- No timing evidence
- Timing equality was not inferred
These records contain no duration measurements, so they establish neither timing equality nor production enumeration resistance. Locale and envelope observations also lie outside the body comparison.
- Account observation
- Body contract known
- Transition/response review
- Internal reasons retained
- Scope conclusion
- UNKNOWN: timing/envelope
- The response review was bounded
One public body contract is supported, with no context issuance. The entire authentication flow is not called uniform without further timing/status/redirect observations.
- Account observation
- Known: public body equal
- Transition/response review
- Known: no context issuance
- Scope conclusion
- UNKNOWN: other response differences
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Relate account transitions to verification decisions
Sanitized application audit can include subjectRef, accountStatus, contactVerified, attemptAllowed, passwordVerified, contextIssued, and verifierGeneration. These fields are not standard log events. Candidate passwords, verifier bytes, and verification tokens stay out of telemetry.
The XPath selects an integrity discrepancy where contextIssued=true and accountStatus is not active. Benign active login selects zero; a supplied disabled-context issuance fixture selects one. Missing status means insufficient evidence. Administrative disablement, closure, and verification delay can be normal explanations; a row does not prove a malicious actor.
Separate response review from rate-limit validation
The public-response matrix expresses absent/wrong/pending/disabled conditions under one contract. Compare text, status, and content envelopes separately. Timing equivalence needs controlled measurements and load conditions; the boolean fixture does not perform those measurements.
For the throttle gate, observe that a blocked attempt advances to neither verification nor context issuance. Node distribution, unavailable limit state, and legitimate retry experience require separate checks; one IP blacklist does not constitute the full design.
Durable controls for verifier migration and disablement
Inventory credential generations and cost-policy metadata. Benchmark the capacity effect of migration, and protect successful-verification rehashing against stale-reset overwrites. Salt/cost aliases must not be copied as production parameters.
Document how disablement/closure affects active credentials, session stores/caches, and recovery eligibility. Preserve necessary subject correlation while reducing unnecessary personal data. Permission to rebind a verification contact does not follow automatically from an existing session.
NOW APPLY IT TO THE CASE
Why does a password match not override account lifecycle?
Contact verification, throttling, password verification and context issuance are different transitions. The scope of equality provided by a generic message is separately bounded.
Separate decisions for verified actor, tenant relation and requested action in the case. Do not substitute authentication or proof success for object permission.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Account-state and verifier-metadata ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"subjectRef": "subject-duru",
"contactAlias": "CONTACT_ALIAS_ONLY",
"accountStates": [
"pending-contact",
"active",
"disabled",
"closed"
],
"credentialRecord": {
"algorithm": "Argon2id illustration",
"salt": "SALT_ALIAS_NOT_BYTES",
"costProfile": "BENCHMARKED_POLICY_ALIAS_NOT_PARAMETERS",
"oldVerifier": "VERIFIER_OLD_ALIAS_NOT_A_HASH",
"currentVerifier": "VERIFIER_CURRENT_ALIAS_NOT_A_HASH",
"plaintextStored": false,
"generation": "credential-generation-a"
},
"registration": {
"identifierAvailable": true,
"candidateAllowed": true,
"initialStatus": "pending-contact",
"contactTokenAlias": "VERIFY_CONTACT_ALIAS_ONLY",
"purpose": "verify-contact",
"subjectMatches": true,
"tokenMatches": true,
"unused": true,
"lifetimeValid": true,
"afterTokenState": "consumed",
"afterStatus": "active",
"sessionIssued": false
},
"loginRows": [
{
"case": "normal",
"accountStatus": "active",
"attemptAllowed": true,
"passwordMatches": true,
"needsRehash": true,
"verifyCalled": true,
"contextIssued": true,
"rehashCommitted": true
},
{
"case": "disabled",
"accountStatus": "disabled",
"attemptAllowed": true,
"passwordMatches": true,
"needsRehash": true,
"verifyCalled": true,
"contextIssued": false,
"rehashCommitted": false
},
{
"case": "throttled",
"accountStatus": "active",
"attemptAllowed": false,
"passwordMatches": null,
"needsRehash": true,
"verifyCalled": false,
"contextIssued": false,
"rehashCommitted": false
}
],
"publicFailures": [
{
"internalReason": "absent-account",
"publicBody": "Authentication could not be completed",
"contextIssued": false
},
{
"internalReason": "wrong-password",
"publicBody": "Authentication could not be completed",
"contextIssued": false
},
{
"internalReason": "pending-contact",
"publicBody": "Authentication could not be completed",
"contextIssued": false
},
{
"internalReason": "disabled-account",
"publicBody": "Authentication could not be completed",
"contextIssued": false
}
],
"selector": {
"language": "XPath",
"xml": "<audit><login accountStatus=\"active\" contextIssued=\"true\"/></audit>",
"xpath": "count(/audit/login[@contextIssued=\"true\" and @accountStatus!=\"active\"])",
"expected": 0,
"positiveFixtureXml": "<audit><login accountStatus=\"disabled\" contextIssued=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Local account/context integrity; no production log, latency, KDF or mail measurement.",
"missingFieldBehavior": "Selection requires declared fields. Missing fields mean insufficient evidence; a zero count does not prove control efficacy."
}
}- accountStatus / contactVerified
- Product eligibility and communication-endpoint control, not identity proofing.
- credentialRecord
- Salt/cost/verifier aliases explain an established-library record; neither real hashes nor production cost settings.
- attemptAllowed / verifyCalled
- A blocked throttle gate leaves verification unexecuted; passwordMatches=null is unknown.
- contextIssued / rehashCommitted
- Context issuance and verifier upgrade are separate outcomes; generation races require separate safeguards.
- publicFailures / selector
- A public contract separated from internal reasons and an owned XML integrity selector.
Account-policy states are authored. Password/hash/token/contact aliases are nonfunctional. Mail, real KDF, session issuance, distributed limiting, and timing measurements are not executed; predicates represent supplied trusted results.
Two reality checks
A matching password must permit a disabled account to log in.
Credential matching and account eligibility are separate gates; disabled state can block new context issuance.
One generic error message proves enumeration risk is eliminated.
Status, timing, size, redirects, and other lifecycle endpoints can differ; text equality is only one piece of evidence.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The account is disabled, but passwordMatches=true. Are a login context and rehash commit produced?
- No. contextIssued=false and rehashCommitted=false.
- Yes. A matching password automatically reactivates a disabled account.
Read the explained answer
No. contextIssued=false and rehashCommitted=false. — This policy issues no context for a disabled account. A matching password does not remove the account-state condition.
Contact verification activated the account. Was an authenticated browser session also issued?
Selected lesson artifact fields; teaching example
- registration.initialStatus
- pending-contact
- registration.purpose
- verify-contact
- registration.afterTokenState
- consumed
- registration.afterStatus
- active
- registration.sessionIssued
- false
- Yes. An active account automatically creates all browser sessions.
- No. The account is active, but sessionIssued=false.
Read the explained answer
No. The account is active, but sessionIssued=false. — The verify-contact proof is consumed for this lifecycle transition. Account activation is not session issuance.
PublicFailures uses one body for four internal reasons. Why does that not prove identical responses or timing, and what do the KDF aliases not measure?
Read the explained answer
The common public message illustrates limiting account-state disclosure. It alone does not prove equal status, timing, retry behavior or other responses. Salt/verifier/cost aliases explain the Argon2id role but are not real hash bytes or benchmark parameters. Internal audit may retain a limited reason without placing passwords or credential material in the public body.
□ I did not treat one public body as proof of timing equality.
□ I did not treat KDF aliases as real hashes or calibrated parameters.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- OWASP — Authentication Cheat SheetUsernames · Compare Password Hashes Using Safe Functions · Authentication and Error Messages · Login Throttling · Require Re-authentication for Sensitive Features
- OWASP — Password Storage Cheat SheetHashing vs Encryption · Salting · Using Work Factors · Upgrading the Work Factor · Password Hashing Algorithms
- NIST SP 800-63B-4 — Authentication and Authenticator Management2.2 Authentication Assurance Level 2 · 3.1.1.2 Password Verifiers · 3.2.2 Rate Limiting (Throttling) · 3.2.5 Phishing Resistance · 4.1 Authenticator Binding · 4.2 Account Recovery · 4.5 Invalidation · 4.6 Account Notifications
- OWASP — Logging Cheat SheetWhich events to log · Event attributes · Data to exclude · Verification