Authentication & Identity
Master authentication factors, password security, MFA, session management, OAuth 2.0, and common identity attacks like credential stuffing and session hijacking.
Learning Objectives
- → Explain the three authentication factors and when to use each
- → Implement secure password storage with bcrypt
- → Describe TOTP-based MFA and why it's more secure than SMS
- → Understand OAuth 2.0 and OpenID Connect flows
- → Identify and defend against credential stuffing, brute force, and session attacks
Authentication vs Authorization
- Authentication — who are you? (identity proof)
- Authorization — what can you do? (permission check)
Authentication comes first. A system might perfectly know who you are (authn) but still let you do things you shouldn't (authz failure).
The Three Factors
| Factor | Type | Examples | Weakness |
|---|---|---|---|
| Something you know | Knowledge | Password, PIN, security question | Stolen, guessed, phished |
| Something you have | Possession | TOTP app, hardware key, SMS | Lost, SIM swapped |
| Something you are | Inherence | Fingerprint, face, voice | Spoofed, can't change if compromised |
MFA = at least two different factors. SMS = "something you have" but weak (SIM swap).
Password Security
What Makes a Strong Password?
Length > Complexity:
"correct horse battery staple" → 44 chars, easy to remember, very strong
"P@$$w0rd" → 8 chars, seems complex, cracked fast
Entropy = log2(charset^length)
lowercase only, 8 chars: log2(26^8) = 37.6 bits (crackable)
any char, 12 chars: log2(95^12) = 78.8 bits (secure)
Password Storage
import bcrypt
def hash_password(password: str) -> str:
salt = bcrypt.gensalt(rounds=12) # 12 rounds = 2^12 iterations
return bcrypt.hashpw(password.encode(), salt).decode()
def verify_password(password: str, hashed: str) -> bool:
return bcrypt.checkpw(password.encode(), hashed.encode())
# Django does this for you:
from django.contrib.auth.hashers import make_password, check_password
hashed = make_password("mypassword") # → "pbkdf2_sha256$..."
check_password("mypassword", hashed) # → True
Password Policy Best Practices
✅ Minimum 12 characters
✅ Check against breached password databases (HaveIBeenPwned API)
✅ No forced rotation (users pick worse passwords when forced)
✅ No complexity requirements (encourages predictable substitutions)
✅ Blocklist common passwords (password123, admin, etc.)
✅ Rate-limit login attempts
❌ Store in plaintext
❌ Store unsalted hashes
❌ Email password reminders
TOTP – Time-Based One-Time Passwords
TOTP (RFC 6238) generates a 6-digit code every 30 seconds:
TOTP = HOTP(secret, floor(unix_time / 30))
HOTP = HMAC-SHA1(secret, counter)[:6 digits]
Both app and server know the secret (stored in QR code at setup)
Both independently compute the same code → match = authenticated
import pyotp
# Setup
secret = pyotp.random_base32() # store in DB per user
totp = pyotp.TOTP(secret)
uri = totp.provisioning_uri("[email protected]", issuer_name="CyberLearn")
# → qrcode.make(uri) to generate QR
# Verify
code = "123456" # from user
totp.verify(code) # True if within ±30 second window
SMS vs TOTP: SMS can be intercepted (SS7 attacks) or SIM-swapped. TOTP requires physical possession of the device running the app.
Session Management
After login, server issues a session token:
1. User logs in with credentials
2. Server verifies → creates session in DB or cache
3. Server sends session ID in cookie: Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
4. Browser sends cookie on every request
5. Server looks up session to identify user
Secure Cookie Flags
Set-Cookie: session=abc123;
HttpOnly; # JS cannot read it (prevents XSS theft)
Secure; # HTTPS only
SameSite=Strict; # No cross-site requests (prevents CSRF)
Path=/;
Max-Age=3600 # 1 hour TTL
Session Attacks
- Session fixation — attacker sets a known session ID before login; after user logs in, attacker uses same ID → always regenerate session ID on login
- Session hijacking — steal session cookie via XSS, network sniffing → use HttpOnly + Secure + HTTPS
- CSRF — trick browser into making authenticated request → SameSite=Strict + CSRF tokens
JWT – JSON Web Tokens
Header.Payload.Signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature
Header: {"alg":"HS256","typ":"JWT"}
Payload: {"user_id":1,"role":"admin","exp":1704067200}
Signature: HMAC-SHA256(base64(header)+"."+base64(payload), secret)
import jwt
# Create
token = jwt.encode({"user_id": 1, "exp": ...}, secret, algorithm="HS256")
# Verify (raises exception if invalid/expired)
payload = jwt.decode(token, secret, algorithms=["HS256"])
JWT pitfalls:
- alg: none attack — if server accepts unsigned tokens
- Weak secret — HS256 with weak secret → brute-forceable
- No expiry — never-expiring tokens
- Sensitive data in payload — payload is base64, not encrypted
OAuth 2.0 & OpenID Connect
OAuth 2.0 = authorization delegation. OpenID Connect = identity layer on top.
"Login with Google" flow (Authorization Code):
1. User clicks "Login with Google"
2. App redirects to Google: /authorize?client_id=...&scope=openid email
3. User authenticates with Google, approves scopes
4. Google redirects to callback: /callback?code=AUTH_CODE
5. App exchanges code for tokens: POST /token (server-to-server)
6. Google returns: access_token, id_token (JWT with user info)
7. App uses id_token to identify user
Security requirements for OAuth:
- Use state parameter to prevent CSRF
- Use PKCE (Proof Key for Code Exchange) for public clients
- Validate id_token signature and iss/aud claims
- Short-lived access tokens + refresh tokens
Common Identity Attacks
Credential Stuffing
Use leaked username/password pairs from one breach to attack other services:
- Defense: breach password detection, MFA, anomaly detection (geo/device)
Password Spraying
Try one common password against many accounts (avoids lockout):
Round 1: try "Summer2024!" on all accounts
Round 2: wait 30 min, try "Welcome1!" on all accounts
- Defense: conditional access policies, MFA, monitor failed logins
Brute Force
Try all combinations:
- Defense: rate limiting, CAPTCHA, account lockout (careful: DoS risk), MFA
Build a Python script that: (1) hashes passwords with bcrypt (rounds=12), (2) checks a given password against HaveIBeenPwned API (using k-anonymity: send first 5 chars of SHA1 hash, check response), (3) enforces minimum 12 chars and blocks the top 20 common passwords, (4) demonstrates correct verify() behavior.
What is k-anonymity in the context of breach checking?
What bcrypt rounds value balances security and performance?
Using pyotp: (1) generate a TOTP secret for a test user, (2) create the provisioning URI and display as QR code, (3) implement verify_mfa(user_id, code) that checks the current and adjacent windows, (4) implement backup codes: generate 8 random one-time codes stored as bcrypt hashes, (5) test that a used backup code cannot be reused.
Why does TOTP allow a ±30 second window?
Given a JWT: eyJhbGciOiJub25lIn0.eyJ1c2VyX2lkIjoxLCJyb2xlIjoiYWRtaW4ifQ. — (1) decode the header and payload (base64), (2) identify the security issue (alg:none), (3) show why a server accepting this would bypass signature verification, (4) fix the code to reject non-HS256/RS256 tokens and verify expiry.
What is the alg:none JWT attack?