Authentication and Authorization
Authentication vs Authorization
Section titled “Authentication vs Authorization”Authentication (AuthN) answers “who are you?” — it verifies identity.
Authorization (AuthZ) answers “what can you do?” — it enforces permissions.
These are distinct concerns that are often conflated. A user can be authenticated (their identity is Verified) but not authorized (they lack permission for a specific action). Conversely, a system Might authorize a request without authentication (anonymous access).
| Aspect | Authentication | Authorization |
|---|---|---|
| Question | Who are you? | What are you allowed to do? |
| Mechanism | Passwords, MFA, certificates | RBAC, ABAC, ACLs, policies |
| Failure mode | Authentication failed | Access denied / Forbidden |
| HTTP status | 401 Unauthorized | 403 Forbidden |
| Frequency | Once per session () | Every request |
| Revocation | Invalidate session/token | Update permissions/policies |
Password Storage
Section titled “Password Storage”How Not to Store Passwords
Section titled “How Not to Store Passwords”| Method | Why It Fails |
|---|---|
| Plaintext | Immediate compromise on any data breach |
| MD5 | Fast, 128-bit output, no salt, broken |
| SHA-1 | Fast, 160-bit output, collision broken |
| SHA-256 without salt | Fast, no salt, vulnerable to rainbow tables |
| Base64 encoding | Not hashing at all — encoding is not encryption |
| Custom encryption | Key management problem shifts the attack surface |
How to Store Passwords
Section titled “How to Store Passwords”Use a dedicated password hashing function with a unique random salt per password:
## Argon2id (recommended)from argon2 import PasswordHasherph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)hash = ph.hash("user_password") # $argon2id$v=19$m=65536,t=3,p=4$...
## bcrypt (widely supported)import bcrypthash = bcrypt.hashpw(b"user_password", bcrypt.gensalt(rounds=12))
# scrypt (memory-hard alternative)import hashlibhash = hashlib.scrypt(b"user_password", salt=os.urandom(16), n=2**14, r=8, p=1, dklen=64)Password Hash Format
Section titled “Password Hash Format”A password hash must contain:
- Algorithm identifier: Which function was used (allows migration)
- Parameters: Cost factor, memory, parallelism (allows increasing work factor)
- Salt: Unique per password (prevents rainbow tables and identical password detection)
- Hash output: The actual derived key
Example formats:
# Argon2id$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
# bcrypt$2b$12$R9h/cIPz0gi.YJJFsRyuPOYfGJTCGijPylCPvmFbzXuKJ5Xo1GZ6u
# scrypt (PHC format)$scrypt$ln=16,r=8,p=1$c2FsdHNhbHQ$E4JnHDMfM/4R5U6YbGzbVg==Hash Migration Strategy
Section titled “Hash Migration Strategy”When you need to upgrade from bcrypt to Argon2id:
- On login, verify the password against the existing hash
- If verification succeeds and the hash uses the old algorithm, re-hash with the new algorithm
- Store the new hash
- Over time, all active users migrate to the new algorithm
This avoids forcing a password reset for all users.
Password Policies
Section titled “Password Policies”NIST SP 800-63B Recommendations (Revised 2023)
Section titled “NIST SP 800-63B Recommendations (Revised 2023)”The NIST Digital Identity Guidelines represent the current best practice for password policies, and They contradict many traditional policies.
Do:
- Require minimum 8 characters (15+ for higher security)
- Allow all printable characters and spaces
- Check passwords against breached password databases (HaveIBeenPwned API)
- Use rate limiting to prevent brute-force attacks
- Allow password managers and paste functionality
- Implement secure password reset (time-limited, single-use tokens)
Do Not:
- Force periodic password rotation (leads to predictable patterns:
Password1!``Password2!…) - Require composition rules (uppercase + lowercase + digit + special character — users just capitalize the first letter and add
1!) - Require passwords to be changed after a breach unless compromise is confirmed
- Use knowledge-based authentication (security questions are guessable)
- Store password hints
- Set maximum password length under 64 characters
Breached Password Checking
Section titled “Breached Password Checking”Check passwords against known breached password databases at creation and authentication time:
import requestsimport hashlibimport sys
def check_pwned_password(password): """Check if password appears in HaveIBeenPwned database using k-anonymity.""" sha1 = hashlib.sha1(password.encode()).hexdigest().upper() prefix, suffix = sha1[:5], sha1[5:] response = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}") for line in response.text.splitlines(): hash_suffix, count = line.split(":") if hash_suffix == suffix: return int(count) return 0Multi-Factor Authentication (MFA)
Section titled “Multi-Factor Authentication (MFA)”MFA requires two or more independent factors from different categories:
| Factor Category | Examples | Security Level |
|---|---|---|
| Knowledge | Passwords, PINs, security questions | Low (phishable) |
| Possession | TOTP apps, hardware keys, phone (SMS) | Medium (varies) |
| Inherence | Biometrics (fingerprint, face, iris) | Medium (not revocable) |
| Location | IP address, geolocation | Low (spoofable) |
TOTP (Time-based One-Time Password)
Section titled “TOTP (Time-based One-Time Password)”TOTP (RFC 6238) generates a 6-8 digit code based on a shared secret and the current time. The server And client both compute:
\mathrm{TOTP = \mathrm{Truncate\Big(\mathrm{HMAC-SHA-1(K, T)\Big)Where is the shared secret and T = \lfloor \mathrm{current\_time / 30 \rfloor.
| Property | Value |
|---|---|
| Time step | 30 seconds |
| Code length | 6 digits (default) |
| Shared secret | 160-bit (Base32) |
| Hash | HMAC-SHA-1 (default), SHA-256, SHA-512 |
Limitations: TOTP codes are phishable. An attacker can proxy the login page and forward the TOTP Code to the real service in real time. TOTP is not a replacement for phishing-resistant MFA.
FIDO2 / WebAuthn
Section titled “FIDO2 / WebAuthn”FIDO2 (Fast Identity Online 2) is the gold standard for phishing-resistant authentication. It uses Public-key cryptography with a hardware authenticator.
sequenceDiagram participant U as User participant C as Client (Browser) participant S as Server
S->>C: Send challenge + allowed credentials C->>U: Prompt for biometric/PIN U->>C: Unlock authenticator C->>S: Send authenticator assertion (signature over challenge) S->>S: Verify signature with stored public key S->>C: Authentication successKey properties:
- Phishing-resistant: The authenticator binds to the relying party (origin), so a phishing site cannot replay the credential.
- Public-key based: The server stores a public key, not a shared secret. Compromising the server does not allow impersonation.
- Hardware-bound: Private key never leaves the authenticator (YubiKey, Touch ID, Windows Hello).
- Multi-device: Passkeys (synced WebAuthn credentials) allow cloud-synced FIDO2 credentials.
Hardware Security Keys
Section titled “Hardware Security Keys”| Key Model | Protocol Support | Connector | Price (approx.) |
|---|---|---|---|
| YubiKey 5 | FIDO2, U2F, OTP, PIV | USB-A/C, NFC | USD 45-55 |
| YubiKey Bio | FIDO2 (biometric) | USB-A/C | USD 80 |
| Titan Key | FIDO2, U2F | USB-A/C, NFC | USD 30-40 |
| SoloKeys | FIDO2, U2F | USB-A/C | USD 25-50 |
SMS-based 2FA
Section titled “SMS-based 2FA”Cross-References
Section titled “Cross-References”- OAuth Deep Dive extends authentication concepts to modern delegated authorisation protocols used by major platforms.
- Cryptography provides the hashing and encryption primitives that secure passwords and authentication tokens.
- Security Fundamentals establishes the confidentiality and access control principles that authentication enforces.