Skip to content

Authentication and 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).

AspectAuthenticationAuthorization
QuestionWho are you?What are you allowed to do?
MechanismPasswords, MFA, certificatesRBAC, ABAC, ACLs, policies
Failure modeAuthentication failedAccess denied / Forbidden
HTTP status401 Unauthorized403 Forbidden
FrequencyOnce per session ()Every request
RevocationInvalidate session/tokenUpdate permissions/policies
MethodWhy It Fails
PlaintextImmediate compromise on any data breach
MD5Fast, 128-bit output, no salt, broken
SHA-1Fast, 160-bit output, collision broken
SHA-256 without saltFast, no salt, vulnerable to rainbow tables
Base64 encodingNot hashing at all — encoding is not encryption
Custom encryptionKey management problem shifts the attack surface

Use a dedicated password hashing function with a unique random salt per password:

## Argon2id (recommended)
from argon2 import PasswordHasher
ph = 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 bcrypt
hash = bcrypt.hashpw(b"user_password", bcrypt.gensalt(rounds=12))
# scrypt (memory-hard alternative)
import hashlib
hash = hashlib.scrypt(b"user_password", salt=os.urandom(16), n=2**14, r=8, p=1, dklen=64)

A password hash must contain:

  1. Algorithm identifier: Which function was used (allows migration)
  2. Parameters: Cost factor, memory, parallelism (allows increasing work factor)
  3. Salt: Unique per password (prevents rainbow tables and identical password detection)
  4. 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==

When you need to upgrade from bcrypt to Argon2id:

  1. On login, verify the password against the existing hash
  2. If verification succeeds and the hash uses the old algorithm, re-hash with the new algorithm
  3. Store the new hash
  4. Over time, all active users migrate to the new algorithm

This avoids forcing a password reset for all users.

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

Check passwords against known breached password databases at creation and authentication time:

import requests
import hashlib
import 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 0

MFA requires two or more independent factors from different categories:

Factor CategoryExamplesSecurity Level
KnowledgePasswords, PINs, security questionsLow (phishable)
PossessionTOTP apps, hardware keys, phone (SMS)Medium (varies)
InherenceBiometrics (fingerprint, face, iris)Medium (not revocable)
LocationIP address, geolocationLow (spoofable)

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 KK is the shared secret and T = \lfloor \mathrm{current\_time / 30 \rfloor.

PropertyValue
Time step30 seconds
Code length6 digits (default)
Shared secret160-bit (Base32)
HashHMAC-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 (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 success

Key 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.
Key ModelProtocol SupportConnectorPrice (approx.)
YubiKey 5FIDO2, U2F, OTP, PIVUSB-A/C, NFCUSD 45-55
YubiKey BioFIDO2 (biometric)USB-A/CUSD 80
Titan KeyFIDO2, U2FUSB-A/C, NFCUSD 30-40
SoloKeysFIDO2, U2FUSB-A/CUSD 25-50

  • 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.