Handbooks / Web Security (OWASP) / Chapter 3

Authentication and Sessions

7 pages · ~5 min✓ Reviewed

Builds on Cross-Site Scripting (XSS). Next up: CSRF and CORS.

Web Security (OWASP)Page 1

Password Storage Mental Model

1. Hash, Don't Encrypt

  • Store a slow, salted hash, never the password
  • Hash = one-way; encryption = reversible
  • Never store an encrypted copy either

2. Salt vs Pepper

SaltPepper
ValueRandom, per userOne app-wide secret
KeptBeside hash, in clearEnv var or KMS
DefeatsRainbow tables, equal hashesDB leak alone
  1. Password
  2. + salt
  3. Slow hash
  4. Store

3. Why Fast Hashes Fail

  • MD5, SHA-1, plain SHA-256 are fast
  • A GPU tries billions per second
  • Use a slow, tunable password hash
Remember

Slow + salted hash in the DB; the pepper lives outside it.

4. Interview Point

  • Why not encrypt? Reversible: one key leak exposes every password.
  • Salt stops what? Rainbow tables; not brute force on a weak password.
  • Salt vs pepper? Salt: per user, stored. Pepper: secret, kept outside the DB.
  • SHA-256 alone? Too fast: GPUs try billions per second.
Web Security (OWASP)Page 2

bcrypt and argon2 in Practice

1. bcrypt

  • Cost n = 2^n rounds; +1 doubles the time
  • Tune to ~250 ms per hash; cost 12 to start
  • Salt is generated and embedded by bcrypt
  • 72-byte limit: input silently truncated; pre-hash or cap length

2. argon2id

  • argon2id is the current default
  • Memory-hard: resists GPU/ASIC attacks
  • Tuned by memory, iterations, parallelism
  • OWASP minimum: 19 MiB, t=2, p=1

3. Usage

  • Verify re-derives the hash from the stored salt
javascript
const hash = await argon2.hash(pw);
const ok = await argon2.verify(hash, pw);
const h2 = await bcrypt.hash(pw, 12);
const ok2 = await bcrypt.compare(pw, h2);
Never ==

Compare hashes with verify/compare, not ==: timing leak.

4. Interview Point

  • Why argon2id? Memory-hard, so GPUs/ASICs gain little
  • Cost factor? 2^n rounds; +1 doubles time
  • Upgrade hashes? Rehash on login when params are old
  • Salt location? Inside the hash string
Web Security (OWASP)Page 3

Sessions vs JWT

1. Session vs JWT

SessionJWT
Client holdsopaque ID cookiesigned token
Server stateyes (shared store, Redis)none
Revokeinstant: delete rownot before exp (denylist)
Payloadstays on serverbase64, readable
  • JWT = header.payload.signature with claims sub, exp
  • Payload is encoded, not encrypted: no secrets in it

2. Expiry and revocation

  1. 1Access tokenabout 15 min
  2. 2ExpiresAPI returns 401
  3. 3Refresh tokenrotates on use
  4. 4New pairold refresh dead

3. Verify every token

  • Pin allowed algorithms; reject alg=none and RS256-to-HS256 confusion
  • Check signature, exp, iss, aud
javascript
jwt.verify(token, publicKey, {
  algorithms: ["RS256"],
  issuer: "auth.example.com",
  audience: "api",
});

exp is checked by default

Logout is not revoke

Deleting the client token does not kill a JWT before exp.

Web Security (OWASP)Page 4

Session Fixation and MFA

1. Session Fixation

  1. Attacker plants IDknown session ID
  2. Victim logs inuses that ID
  3. Server keeps IDnow authenticated
  4. Attacker shares sessionsame ID
  • Fix: regenerate the ID on login and on any privilege change
  • Invalidate the old ID
  • Never accept IDs from URLs

2. Cookie Hardening

SettingEffect
HttpOnlyNo JS access
SecureHTTPS only
SameSite=Lax / StrictCSRF defence
Idle + absolute timeoutCaps a stolen session's life

3. MFA Factors

Something you know, something you have, something you are.

RankFactorCatch
1 (weakest)SMS OTPSIM-swap
2TOTP app30s codes, shared secret
3WebAuthn / passkeysPhishing-resistant

4. Interview Point

  • Fixation fix? Regenerate the session ID on login.
  • HttpOnly? Stops JS reading the cookie (XSS theft), not XSS actions or CSRF.
  • Why is SMS weakest? SIM-swap, and codes can be phished.
  • Recovery codes? Single-use, stored hashed, shown once; regenerate after use.
Web Security (OWASP)Page 5

Broken Access Control and IDOR

1. Who vs What

  • Authentication: who you are
  • Authorization: what you may do
  • Broken access control (OWASP A01): missing or wrong server-side check
  • Hidden buttons and UI checks are not security

2. IDOR and the Fix

  1. GET /invoices/101
  2. Change to 102
  3. No owner check
  4. Other user's data
  • Check ownership server-side on every request
  • Deny by default
  • Use the session identity, not a client ID
sql
SELECT * FROM invoices
WHERE id = ?
AND owner_id = session.user_id

no row = 404

3. Escalation and Interview Point

ExampleTest
Horizontaluser A reads user Bswap B's ID
Verticaluser hits /admincall admin route as user
UUIDs are not a fix

Random IDs only obscure the object; the missing check remains.

  • IDOR vs escalation? IDOR swaps an object ID; escalation gains a higher role
  • Test IDOR? Two accounts: replay A's request with B's ID
  • UUIDs enough? No, only hides IDs
  • Checks live where? Server, every request
Web Security (OWASP)Page 6

Auth Security Cheat Sheet

1. Passwords

  • argon2id or bcrypt cost 12+
  • Unique salt per user; optional pepper in KMS
  • Constant-time compare, never ==
  • Rehash on login when parameters change

2. Sessions, JWT, Cookies

SessionJWT
HoldsOpaque IDSigned claims
RevokeDelete rowShort exp + refresh
VerifyStore lookupPin alg, check exp
SecretsServer sideNever in payload
  • Cookie: HttpOnly, Secure, SameSite=Lax
  • New session ID on login and privilege change (stops fixation)

3. Access Control

  • Deny by default
  • Check ownership server-side every request
  • Use session identity, not client IDs
  • MFA for sensitive actions, prefer WebAuthn
IDOR

WHERE id = ? AND owner_id = session.user_id. UUIDs only hide IDs.

4. Interview Point

  • Secure login? Rate-limit, verify hash, new session ID, secure cookie, MFA
  • Top three mistakes? Fast hashes, unpinned JWT or loose cookies, missing ownership checks
  • Audit IDOR? Two accounts: replay A's IDs with B's session; expect 403
Web Security (OWASP)Page 7

Check yourself

A site keeps the same session ID after login. An attacker planted sid=abc in the victim's browser first. What happens?
  • The attacker's sid=abc becomes the logged-in session.
  • Fix: regenerate the ID on login and invalidate the old one.
Invoices use random UUIDs, but the query is WHERE id = ?. User A pastes B's invoice UUID. What happens?
  • B's invoice is returned: UUIDs hide IDs, they do not authorize.
  • Add AND owner_id = session.user_id.
bcrypt cost goes from 12 to 14. Roughly how does hash time change?
  • About 4x longer: cost n means 2^n rounds.
  • Old hashes still verify, so rehash on next login.