Sessions and JWT
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 21 min · 5 figures

    Authentication: sessions, tokens and JWT

    How a system proves who a user is and remembers it on every request: password hashing, server-side sessions and cookie flags, opaque tokens versus JWTs, signing and validation, revocation with rotating refresh tokens, and where browsers should keep credentials.

    Authentication is how a system proves who a user is, then remembers that proof on every later request. The proving happens once, with a password, an OTP or a passkey. The remembering is a credential sent with each request: a random session ID the server looks up, or a signed token such as a JWT that any service can verify alone. Most design choices, and most interview follow-ups, concern the second part: where the state lives, how fast you can revoke it, and what a stolen copy is worth.

    Picture it first: the key card and the wristband

    Check in at a hotel. The desk looks at your passport once: that is authentication. You get a key card holding only a number, and every door you tap asks the hotel’s system whether that number is still valid. Lose the card and the desk cancels the number at once.

    At a music festival, the gate checks your ticket and snaps on a wristband printed “VIP · valid until Sunday”, with a hologram that is hard to fake. Guards at every stage glance at it and wave you in without radioing the gate, which scales to a hundred stages. But if you are thrown out on Saturday, the band still says Sunday.

    Map it back. The passport check is login. The key card is a server-side session: a meaningless ID whose truth lives in a store consulted on every request. The wristband is a self-contained token such as a JWT: claims trusted because of a signature, checked without calling home, and hard to take back. Which doors either one opens is authorisation, a separate question.

    Authentication, authorisation and the remembering problem

    Authentication (AuthN) answers “who is this?” and ends with a verified identity, such as user u_8f3a21. Authorisation (AuthZ) answers “may this identity do this to that resource?”, such as reading order 991. A valid login says nothing about who owns order 991; skipping that second check is how cross-account leaks happen (see multi-tenancy and isolation).

    HTTP is stateless, so a second after a successful login, GET /orders arrives with no memory of it. The client must present a credential on every request, and the server recognises it in one of two ways: look it up (session IDs, opaque tokens) or verify it cryptographically (signed tokens).

    Third-party access and “Sign in with Google”-style single sign-on belong to OAuth 2.0 and OpenID Connect; services proving who they are to each other is service-to-service auth and mTLS.

    Step one: storing and checking passwords

    Store a slow, salted hash, never the password, so a stolen database does not hand the passwords over. Fast hashes such as SHA-256 are the wrong tool: their speed is what a GPU cracker wants. The OWASP Password Storage Cheat Sheet’s current minimums:

    • Argon2id, the first choice: 19 MiB of memory, 2 iterations, parallelism 1.
    • scrypt if Argon2id is unavailable: N = 2^17 (128 MiB), r = 8, p = 1.
    • bcrypt for legacy systems: work factor 10 or more, passwords capped at 72 bytes.
    • PBKDF2-HMAC-SHA256 at 600,000 iterations where FIPS-140 is required.

    A salt, random per user and stored with the hash, defeats precomputed tables; libraries generate it for you. A pepper is one secret kept in a vault or HSM rather than the database, so a SQL-injection dump alone cannot be cracked. The hash string records its parameters, so you can re-hash with stronger settings at each user’s next login.

    Memory-hard hashing makes login deliberately expensive: a tatkal-style 10 a.m. rush with 1,000 logins hashing at once needs 1,000 × 19 MiB ≈ 19 GiB of RAM across the login fleet. That makes login a denial-of-service target as well as a guessing target. Credential stuffing replays username–password pairs leaked elsewhere, from thousands of IPs; answer it with rate limits per account, IP and device, one generic error for “no such user” and “wrong password”, a breached-password check, and MFA.

    Server-side sessions

    After the password checks out, the classic answer is a server-side session: store what you know about the login under a random ID, and give the browser only the ID, in a cookie.

    A server-side session from login to logout. The browser only ever holds a random ID; everything the server knows lives in Redis under that ID.Notice Logout is a delete on the server. Clearing the cookie alone would leave a stolen copy of the ID working.

    The session ID

    OWASP asks for at least 64 bits of entropy from a cryptographically secure generator, and 128 bits or more if you generate IDs yourself. Its estimate for 64 bits: 2^64 ≈ 1.8 × 10^19 IDs; with 100,000 live sessions and 10,000 guesses a second, a hit takes 1.8 × 10^19 ÷ 10^5 ÷ 10^4 ≈ 1.8 × 10^10 s, about 585 years. Using 32 random bytes (256 bits) costs nothing: 43 characters in base64url. The ID itself must mean nothing.

    The record lives in Redis with a TTL equal to the idle timeout, pushed forward on each request:

    SET sess:k7Qm3vX9…  {"uid":42,"created":1793075400,"mfa":true,"csrf":"…"}  EX 1800

    A Bengaluru SaaS startup with 2 million active sessions at about 300 bytes each needs 2,000,000 × 300 B = 600 MB: one primary plus a replica. If read access to the store or its backups worries you, OWASP suggests storing only a SHA-256 hash of the secret part.

    Set-Cookie: __Host-sid=k7Qm3vX9…; Path=/; Secure; HttpOnly; SameSite=Lax
    Attribute Effect Protects against
    HttpOnly Scripts cannot read the cookie XSS copying the ID out
    Secure Sent only over HTTPS Sniffing on café Wi-Fi
    SameSite=Lax or Strict Withheld from most cross-site requests CSRF (first layer)
    __Host- prefix Browser enforces Secure, Path=/, no Domain A sibling subdomain overwriting it
    No Domain Sent only to the exact host blog.example.in seeing the session

    Strict never sends the cookie on requests started from another site, so a user clicking an emailed link lands logged out. Lax still sends it on top-level GET navigations, but not on cross-site POSTs, images or fetch calls. OWASP prefers Strict, accepts Lax, and says to set one explicitly.

    Lifecycle: fixation, timeouts and logout

    • Regenerate the ID at login and on every privilege change (MFA done, admin mode, password change). Otherwise an attacker who planted a known ID before login (session fixation) inherits your session.
    • Idle timeout: OWASP suggests 2–5 minutes for high-value apps such as net banking, 15–30 for low-risk ones. Absolute timeout: a hard cap since login, such as 4–8 hours. Enforce both on the server.
    • Logout deletes the record, not just the cookie. A per-user set of IDs lets a password change log out everywhere.

    Scaling sessions: sticky or shared

    Sticky sessions pin each user to one server: no shared store, but uneven load, and every deploy or crash logs that server’s users out. A shared store lets any server answer, for one network hop per request and a critical dependency you must replicate. Most teams pick the shared store; see stateful vs stateless design and distributed caching.

    Tokens: look it up, or carry the proof

    APIs, mobile apps and backend services usually send a bearer token in an Authorization: Bearer … header instead of a cookie. Whoever holds a bearer token can use it, so treat it like a password in transit, in logs and at rest.

    An opaque token is a random string like a session ID; the API looks it up in the issuer’s store or asks the issuer’s introspection endpoint, caching the answer briefly. Revocation is instant; every check costs a lookup.

    A self-contained token carries its claims (who, for which API, until when) plus a signature, so any service holding the issuer’s public key verifies it locally. That is the JWT’s big win and its catch: the issuer cannot take it back before it expires.

    Two ways to recognise a request behind the gateway. On the left every service asks a shared session store; on the right every service verifies a signed token with public keys it has cached.Notice The right side has no box on the request path that all services depend on. The auth service only signs tokens and publishes keys.

    Anatomy of a JWT

    A JSON Web Token (RFC 7519) in its usual signed form is three base64url strings joined by dots, header.payload.signature: the JWS compact serialisation from RFC 7515. The header says how it was signed, the payload holds the claims, and the signature covers both.

    One ES256-signed access token, split at its two dots and decoded. The signature is computed over the first two segments exactly as they appear in the token.Notice Decoding needs no key at all. The key only matters for producing and checking the signature.

    Decoded, the full header and payload of that token are:

    { "alg": "ES256", "typ": "at+jwt", "kid": "2026-10-a" }
    
    {
      "iss": "https://auth.example.in",
      "sub": "u_8f3a21",
      "aud": "https://api.example.in",
      "iat": 1793075400,
      "nbf": 1793075400,
      "exp": 1793076300,
      "jti": "0b9e6c42-7d1f-4a8e-9c35-2f61d0a7e4b8",
      "scope": "orders:read orders:write",
      "ver": 7
    }

    RFC 7519 registers seven claims, all optional in the spec, so your validation rules must make the important ones required:

    Claim Means Verifier checks
    iss Issuer Exactly your auth server
    sub Subject, usually the user Exists and is active
    aud Intended recipients Includes your service
    exp Expiry (seconds since 1970) Now is before it, with small leeway
    nbf Not valid before Now is at or after it
    iat Issued at Not in the future
    jti Unique token ID Denylist or replay key

    Here exp − iat = 1,793,076,300 − 1,793,075,400 = 900 s, a 15-minute token; scope and ver are private claims for permissions and revocation. With its 64-byte ES256 signature the token is 446 characters; an RS256 signature (256 bytes) takes it to about 700. The session cookie was 43, and every request carries the difference.

    Signed is not encrypted

    Anyone holding the token can base64-decode the payload; no key needed. Never put a phone number, an Aadhaar-style ID or an address in a JWT. JSON Web Encryption (JWE) exists, but the simpler fix is to keep sensitive data server-side and put only an ID in the token.

    Signing: shared secret or key pair

    HS256 (HMAC-SHA-256) signs and verifies with one shared secret, so every service that can verify a token can also mint one: if twelve services share the key, a bug in any of them lets an attacker forge tokens all twelve accept. Use it only when the issuer is the sole verifier, with a random key of at least 256 bits; RFC 8725 warns that a weak HMAC key can be brute-forced offline from one captured token.

    Asymmetric algorithms split the roles. The auth service signs with a private key that never leaves it, ideally in a KMS or HSM (see encryption and key management); everyone else verifies with public keys published as a JSON Web Key Set (JWKS). RS256 is still very common, but OWASP’s JWT cheat sheet now marks it “not recommended” and lists ES256, PS256 and EdDSA instead.

    Key rotation rides on the kid header: the JWKS lists several keys and each token names its signer. Publish the new public key, wait one JWKS cache lifetime, switch signing, and remove the old key once its last token has expired. With a 1-hour cache, 15-minute tokens and 60 s of leeway: add the key at T − 60 min, switch at T, remove the old one at T + (15 + 1) min = T + 16 min.

    Validating a JWT, and the classic attacks

    The classic JWT attacks never break the cryptography; they exploit verifiers that let the token say how to verify it. A safe verifier trusts only its own configuration:

    1. Pin the algorithm per key, say ES256; never act on the token’s alg, and never accept none.
    2. Take keys only from your issuer’s JWKS, using kid just to choose among them. Ignore keys or key URLs inside the token (jwk, jku, x5u).
    3. Verify the signature, then the claims: exact iss, your identifier in aud, exp and nbf within under a minute of leeway, the expected typ.
    4. Apply your own rules: user active, token version current, jti not denylisted, scopes sufficient.

    With PyJWT:

    import jwt  # PyJWT 2.x
    
    ISSUER = "https://auth.example.in"
    AUDIENCE = "https://api.example.in"
    jwks = jwt.PyJWKClient(f"{ISSUER}/.well-known/jwks.json")  # caches the key set
    
    def verify_access_token(token: str) -> dict:
        signing_key = jwks.get_signing_key_from_jwt(token)  # by kid, from OUR issuer only
        decoded = jwt.decode_complete(
            token,
            signing_key.key,
            algorithms=["ES256"],   # pinned: the token's alg header is not trusted
            issuer=ISSUER,
            audience=AUDIENCE,
            leeway=30,              # seconds of clock skew
            options={"require": ["exp", "nbf", "iat", "iss", "aud", "sub", "jti"]},
        )
        if decoded["header"].get("typ") != "at+jwt":
            raise jwt.InvalidTokenError("not an access token")
        claims = decoded["payload"]
        if claims["ver"] != current_token_version(claims["sub"]):  # cached per user
            raise jwt.InvalidTokenError("token revoked")
        return claims

    Each attack catalogued in RFC 8725 breaks one of those steps:

    Attack What goes wrong Defence
    alg: none An unsigned token is treated as verified Pinned algorithm list
    RS256 → HS256 confusion Attacker HMAC-signs with your public key as the secret; a naive library verifies with that same key One algorithm per key; never mix HMAC and public-key algorithms
    No aud / iss check A token for a low-risk service is replayed against payments Require both; one audience per API
    kid / jku tricks kid reaches SQL or a file path; jku points at the attacker’s keys Keys only from your JWKS; sanitise kid
    Weak HMAC secret “MyProject2026” is cracked offline 256-bit random secret, or key pairs
    Cross-JWT confusion A password-reset or ID token passes as an access token Distinct typ, aud and keys per token kind

    Decoding is not verifying

    Every JWT library can decode without verifying, for debugging. If that call ever reaches a request path, users can edit their own sub or scope and be believed.

    The revocation problem

    A session dies when you delete its record. A stolen JWT works until exp, even after logout or a password change, and every fix gives back some of the no-lookup benefit.

    Short-lived access tokens, 5–15 minutes, cap how long a stolen one is useful. You rarely revoke them; you stop renewing them.

    Refresh tokens keep the user signed in. They last days or weeks with an idle expiry, go only to the auth service, and are checked against its store: a lookup, but once per 15 minutes, not per request. They are usually opaque, stored hashed and tied to one client.

    Rotation with reuse detection. RFC 9700, the OAuth 2.0 Security Best Current Practice (January 2025), requires refresh tokens for public clients, such as browser and mobile apps, to be sender-constrained or rotated. With rotation, every refresh returns a new token and invalidates the old one, and the server remembers which tokens descend from one login: the family. If a used token comes back, someone is replaying it. The server cannot tell who, so it revokes the family.

    Refresh-token rotation over one hour. An attacker steals RT2 and redeems it first; when the real app later presents RT2 too, the reuse gives the theft away and the whole family is revoked.Notice The attacker still holds a working access token for up to 15 minutes. Rotation detects the theft; only a short access-token lifetime limits the damage.

    Mobile apps on flaky networks may retry a refresh with the same token, so some implementations allow a few seconds of grace. RFC 9700 also allows revoking the family on password change and logout.

    Denylist by jti. For “kill this token now”, push its iss and jti onto a denylist whose entries expire at the token’s exp. Key it on those claims, not a hash of the token string: OWASP warns that a revoked JWT can be re-encoded so its hash changes while its signature still verifies. The list stays tiny: 3,000 revocations an hour × a 15-minute lifetime = 3,000 × 0.25 ≈ 750 live entries, easy to hold in every gateway’s memory.

    Per-user token version. Put ver in each token and the current version on the cached user record; “log out everywhere” increments it, killing every outstanding token. That is a lookup again, but one per user, not per token.

    In the interview

    Say the trade-off: “JWTs give me verification without a lookup, but revocation can lag by one access-token lifetime. I keep that to 15 minutes, rotate refresh tokens with reuse detection, and keep a jti denylist for emergencies. If every request needs instant revocation, I’ll use sessions.”

    Where a browser should keep its credential

    localStorage or sessionStorage: any script on your origin can read them, including one injected through XSS or a compromised third-party library. One fetch later the token is on the attacker’s server, replayable from anywhere until it expires. OWASP says never keep tokens there.

    A variable in memory: gone on reload and never on disk, but still reachable by code in the page, and every load needs a refresh.

    An HttpOnly cookie: script cannot read it, so XSS cannot carry it away. The price is CSRF: browsers attach cookies automatically, so an evil page can make the victim’s browser send a convincing request. Layer the OWASP CSRF cheat sheet’s defences: SameSite; a CSRF token (a synchronizer token, or an HMAC-signed double-submit token bound to the session) sent in a custom header on every state-changing request; and an Origin or Sec-Fetch-Site check.

    The same XSS bug against two storage choices. A readable token leaves the browser for good; an HttpOnly cookie stays put, and SameSite keeps it off cross-site POSTs.Notice Neither choice is free: localStorage avoids CSRF but loses the token to any XSS; the cookie resists XSS theft but needs CSRF defences.

    HttpOnly cookies still ride along on fetch calls, so XSS can act as the user inside the open tab, but the damage stays there instead of travelling. You still need output encoding and a Content Security Policy. For single-page apps using OAuth, the recommended shape is a backend-for-frontend (BFF) that holds the tokens while the browser holds only a session cookie; see OAuth 2.0 and OpenID Connect.

    MFA and passkeys

    A second factor makes a leaked password insufficient, but factors differ:

    • SMS OTP is everywhere; many Indian banking, wallet and shopping apps log you in with a phone number and an SMS code. The code is only as safe as the SIM: SIM swaps, number porting and SS7 interception let attackers receive it, so NIST classifies SMS as “restricted” and OWASP advises against it for financial or PII-heavy apps. If you must use it, rate-limit sends, watch for recent SIM changes, and never let SMS alone reset a password.
    • TOTP apps derive a code from a shared seed every 30 seconds (RFC 6238’s default). No SIM to steal, but a phishing page can still relay the code in time.
    • Passkeys (FIDO2/WebAuthn) use a key pair per website, scoped to its domain. At login the device signs a fresh server challenge after a fingerprint, face or PIN check, so a lookalike domain gets nothing usable and real-time phishing proxies come away empty. When a passkey counts as MFA, check the user-verification flag.

    MFA protects the login, not the session that follows, and phishing kits now steal that session directly. Pair it with short sessions and re-authentication before sensitive actions such as adding a payee.

    Sessions vs opaque tokens vs JWT

    Server-side session Opaque bearer token Self-contained JWT
    Where state lives Session store, such as Redis Token store at the issuer Inside the token; the issuer keeps only keys
    Credential size Cookie, ~43 characters Bearer header, ~43 characters ~450–700 characters, more with extra claims
    Per-request check Store lookup, a ~0.2 ms network hop Lookup or introspection, often cached briefly Local signature check, tens of µs
    Revocation Instant: delete the record Instant, minus cache time Waits for exp, unless you add a denylist
    Store or issuer down Everyone looks logged out Same Still verifies with cached keys
    Mobile, cross-domain, APIs Awkward: cookies are per site Good Good: anyone can verify
    Typical use First-party web apps Public APIs needing instant revocation Short-lived tokens at gateways and between services
    Three ways to remember a login. Choose per client type; many systems use two of them.

    A common, perfectly good answer uses two: a session cookie for the browser, and a short-lived gateway-minted JWT for internal calls.

    Going deeper: authentication at scale

    Verify once at the edge

    Verify tokens in the API gateway so bad ones die early, and forward the verified identity. Services behind it should still verify a token, the original or a gateway-minted one, rather than trust a plain X-User-Id header that anything inside the network could forge; see service-to-service auth.

    What a check really costs

    A session lookup is a network round trip; Redis’s latency guide puts a typical 1 Gbit/s hop at about 200 µs. A signature check is local CPU: on one 2026 laptop core, openssl speed rsa2048 ecdsap256 reports roughly 15 µs per RSA-2048 verification and 60 µs per ECDSA P-256 verification (run it on your own servers). At 20,000 requests per second, ES256 costs 20,000 × 60 µs = 1.2 CPU-seconds per second, about one core fleet-wide. The real difference is failure: if the session store is down nobody is logged in; if the auth service is down, JWT holders keep working until their tokens expire.

    Token bloat and clocks

    Teams that copy group memberships into tokens hit limits fast: 200 group UUIDs at about 39 bytes each is 7,800 bytes of JSON, which base64url inflates by 4/3 to about 10,400 characters. By default nginx rejects a request header over 8 KB, and browsers only promise 4,096 bytes per cookie. Keep tokens to identity, audience and coarse scopes.

    exp and nbf are compared with the verifier’s clock. RFC 7519 allows leeway “usually no more than a few minutes”; 30–60 seconds is plenty when hosts run NTP, and anything larger silently lengthens every token’s life.

    Spotting account takeover

    Watch for failed-login bursts across many accounts from one network, a new device in a new country minutes after a login from Pune, refresh-token reuse, and a password reset followed by an email or phone change. Respond in proportion: step-up MFA, revoke sessions and refresh families, and alert the user on another channel.

    Cheat sheet

    Topic Remember it as
    AuthN vs AuthZ Who you are vs what you may do; check both
    Passwords Argon2id (19 MiB, t = 2, p = 1) or stronger; salt; pepper in a vault
    Session ID ≥ 64 bits from a CSPRNG (use 128+); regenerate at login
    Session cookie __Host- prefix, Secure, HttpOnly, SameSite=Lax or Strict, Path=/
    Timeouts Idle 2–5 to 15–30 min; absolute 4–8 h; server-side
    JWT header.payload.signature; signed, not encrypted
    Always check Pinned alg; key from your JWKS by kid; iss, aud, exp, nbf, typ
    Signing HS256 only if issuer is sole verifier; else ES256, PS256 or EdDSA
    Lifetimes Access 5–15 min; refresh rotated on every use; reuse ⇒ revoke the family
    Revocation tools Delete the session · short exp · jti denylist · per-user token version
    Browser storage HttpOnly cookie plus CSRF defences, or a BFF; never localStorage
    MFA Passkeys > TOTP > SMS; guards login, not the session

    How to say it in the interview

    “I split it into proving identity and remembering it. Passwords get Argon2id, logins are rate-limited, and passkeys are the phishing-resistant second factor. For the web app, login creates a 256-bit random session ID, kept in Redis with idle and absolute timeouts and sent in a __Host- cookie that’s HttpOnly, Secure and SameSite=Lax, plus a CSRF token on writes; logout deletes the record. Mobile and internal APIs get 15-minute ES256 JWTs, verified locally against a cached JWKS: alg, iss, aud, exp, typ. Refresh tokens rotate on every use, and reuse revokes the family. For an instant kill, I add a jti denylist or a per-user token version.”

    How do you log out a user who holds a JWT? Revoke their refresh-token family and let the access token expire within 15 minutes. If that is too long, denylist its jti at the gateway or bump the user’s token version, accepting a lookup.

    Why not keep the access token in localStorage? Any script on the page can read it, so one XSS bug or poisoned dependency ships it to an attacker who replays it anywhere until it expires. An HttpOnly cookie can’t be read by script; the price is well-understood CSRF defences.

    HS256 or RS256? HS256 only if the issuer is the sole verifier, because anyone with the secret can mint tokens. With several verifiers, use key pairs and a JWKS; I’d pick ES256 for smaller signatures, RS256 where a library requires it, and rotate keys by kid.

    How do refresh tokens get stolen, and how would you notice? Device malware, a token logged by mistake, a leaky backup, or XSS against script-readable storage. With rotation, thief and client hold the same token, so whoever presents it second triggers reuse detection and the family is revoked. Sender-constrained tokens go further: a stolen one is useless without the device’s key.

    Sessions or JWTs for a new web app? Sessions, for one first-party web app: instant revocation, a tiny cookie, a cheap lookup. JWTs pay off when many services must verify identity without calling home.

    Where to go next

    References and further reading

    1. RFC 7519 — JSON Web Token (JWT)rfc-editor.orgThe compact format, the seven registered claims (iss, sub, aud, exp, nbf, iat, jti), and the small clock-skew leeway, “usually no more than a few minutes”.
    2. RFC 8725 — JSON Web Token Best Current Practicesrfc-editor.orgThe classic attacks (alg none, RS256-to-HS256 confusion, weak HMAC keys, substitution, cross-JWT confusion, kid and jku abuse) and their mitigations, including explicit typing.
    3. RFC 9700 — Best Current Practice for OAuth 2.0 Security (January 2025)rfc-editor.orgRefresh tokens for public clients must be sender-constrained or rotated; how rotation detects replay and revokes the active token; revocation on password change or logout.
    4. OWASP Session Management Cheat Sheetcheatsheetseries.owasp.org64-bit entropy minimum and the 585-year guessing estimate, cookie attributes and the __Host- prefix, ID renewal after login, idle and absolute timeout ranges, hashed session storage, and no tokens in localStorage.
    5. OWASP Password Storage Cheat Sheetcheatsheetseries.owasp.orgCurrent minimum parameters for Argon2id, scrypt, bcrypt and PBKDF2, plus salting, peppering and upgrading work factors at the next login.
    6. OWASP JSON Web Token Cheat Sheetcheatsheetseries.owasp.orgMAC versus signature trade-offs, HMAC secret sizing, the algorithm status table (RS256 not recommended), key-confusion and header-key attacks, and a jti denylist keyed on iss + jti rather than a token hash.
    7. OWASP Cross-Site Request Forgery Prevention Cheat Sheetcheatsheetseries.owasp.orgSynchronizer tokens, the signed double-submit cookie, tokens in custom headers, SameSite as defence in depth, and Fetch Metadata checks.
    8. MDN Web Docs — Set-Cookie headerdeveloper.mozilla.orgExact SameSite Strict and Lax behaviour (top-level navigations with safe methods), cookie name prefixes, and that HttpOnly cookies are still sent with fetch().
    9. OWASP Multifactor Authentication Cheat Sheetcheatsheetseries.owasp.orgTOTP and its 30-second default step, SMS as a NIST-restricted authenticator (SIM swap, SS7, porting), passkeys as phishing-resistant MFA, and reverse-proxy phishing.
    10. Redis documentation — Diagnosing latency issuesredis.ioA typical 1 Gbit/s network adds about 200 µs per round trip, the floor under every session lookup.