Stage 6 · L25
Separate authentication from authorization and hashing from encryption
Core · original Session 6
Use the starter for this stage's focused examples. The cumulative transfer and solution belong at the stage-end capstone. Baseline checks pass; transfer checks initially fail. Downloads contain the matching native starter and solution for this stage.
Authentication establishes the current caller's identity; authorization decides what that caller may do. Logging in does not grant every action or record. This stage establishes local identity and an active-account boundary; stage 07 adds roles and record scope. The existing website account system is a separate Django implementation, not this teaching API.
A password must be verified against a slow password hash rather than stored or compared as plaintext. PasswordHash.recommended uses the installed Argon2 implementation. The registration service unwraps SecretStr only at the hash call and stores the encoded hash. SecretStr masks ordinary representation; it does not encrypt the input or automatically sanitize every validation response.
An unknown email still runs password verification against DUMMY_HASH, so the simplest missing-user timing shortcut is removed. That does not prove the entire endpoint is constant-time across network, database and hashing conditions. Unknown email, wrong password and inactive user share a small401 response with WWW-Authenticate: Bearer. Do not tell an unauthenticated caller which account exists.
The login contract is URL-encoded username=email and password. The pinned shared runtime has no python-multipart package, so identity.login_form implements a narrow streaming-bounded URL-encoded parser. It caps bytes/fields, rejects repeated or extra fields, and does not echo submitted credentials. JSON gets415; malformed form gets422. It is not a multipart parser or a complete OAuth authorization server.
The application also sanitizes request-validation details to loc/type/msg, removing echoed input values. The baseline checks that a short password never appears in its422 body. A password hash, token, Authorization header and reset link must likewise stay out of public output and routine logs. Later observability uses a positive field allowlist rather than trying to redact everything after logging.
Password salt is random per hash operation. It makes equal passwords produce different encoded hashes and prevents a precomputed hash table from being reused unchanged across every account. The encoded Argon2 result includes algorithm parameters and salt; the verifier reads them to check a submitted password. Salt need not be a separate secret column and is not an encryption/signing key.
Hash the same synthetic string twice, compare only equality, then verify it against both stored encodings. Verification succeeds despite different strings. Rehash-and-string-compare is wrong because it generates a new salt. Protect the stored hash from public output and offline guessing; a salt does not make a weak password strong.
Follow the running code
Focused lesson example; see the end-of-stage capstone for the cumulative app · stage 06
from pwdlib import PasswordHash
passwords = PasswordHash.recommended()
first = passwords.hash('synthetic-password-only')
second = passwords.hash('synthetic-password-only')
assert first != second
assert passwords.verify('synthetic-password-only', first)
assert passwords.verify('synthetic-password-only', second)Predict and observe this focused example using the concepts explained above. Its boundary is stated in the focused answer.
Guided lab
- Read the explanation and predict the focused example’s outcome.
- Explain why the two encoded hashes differ and why login must verify against the stored hash rather than compare a freshly generated string.
- Compare the observed outcome with the focused answer and state its boundary.
Expected: Each hash uses a fresh random salt. The encoded Argon2 string carries algorithm parameters and salt so verify can reproduce the check against that stored value. Salt is not the signing key, encryption or a secret password substitute. Unknown-user dummy verification limits an obvious cheap rejection path; it does not guarantee constant total latency.
- SecretStr alone does not sanitize all validation errors; unknown-user hashing is not a whole-system timing certificate.
Focused exercise and answer
Complete this focused exercise before reading its answer. The full native transfer is introduced only at the end of the stage.
Your transfer task: Explain why the two encoded hashes differ and why login must verify against the stored hash rather than compare a freshly generated string.
- Explain why the two encoded hashes differ and why login must verify against the stored hash rather than compare a freshly generated string.
Inspect the matching answer
This answer addresses the focused exercise above; the cumulative implementation is shown only after the stage prerequisites.
Each hash uses a fresh random salt. The encoded Argon2 string carries algorithm parameters and salt so verify can reproduce the check against that stored value. Salt is not the signing key, encryption or a secret password substitute. Unknown-user dummy verification limits an obvious cheap rejection path; it does not guarantee constant total latency.Check your reasoning
Does a valid login decide which rental may be read?
Show the explanation
No. It establishes identity; record scope and action permissions are separate decisions in stage07.
Reading progress
54 lessons remain open to guests. Marking a lesson read records reading only; it does not award assessment credit or a certificate.
Device reading marks require browser storage. Reading is always available.
Sign in or create an account to save separate account progress. Your current page is kept.
Your earlier place on this device suggests these lessons. No new lesson is marked read.