The Story
In April 2014, a security researcher disclosed Heartbleed — a bug in OpenSSL that allowed anyone on the internet to read 64KB of arbitrary server memory per request, including private keys, passwords, and session tokens. The cause was a single missing bounds check in the TLS heartbeat extension. The bug had existed for two years in software used by roughly 66% of all web servers. One missing validation — a failure to verify that the declared payload length matched the actual payload — exposed the authentication infrastructure of the entire internet. It took weeks to patch, and even then, any private key that had been in memory during those two years had to be assumed compromised.
Every distributed system communicates over networks that are fundamentally untrusted. Any packet traveling between two machines can be intercepted, modified, or replayed by an adversary sitting on the same network. Cryptography solves the problem of establishing secure communication over these hostile channels. Authentication solves the problem of proving identity once a secure channel exists.
These two concerns are deeply intertwined: you cannot authenticate securely without cryptography, and cryptographic protocols like TLS embed authentication within their handshake. Understanding both at a mechanical level is essential for designing secure systems.
1. Symmetric Cryptography
- Symmetric cryptography uses a single shared key for both encryption and decryption. If Alice and Bob both possess the same secret key, Alice can encrypt a message and only Bob (who has the same key) can decrypt it.
- The advantage is speed. Algorithms like AES perform bitwise operations (substitution, permutation, XOR with round keys) that modern CPUs execute extremely efficiently, often with dedicated hardware instructions.
- The fundamental problem is key distribution: how do Alice and Bob agree on a shared secret without an eavesdropper learning it? If they already had a secure channel to exchange the key, they wouldn’t need encryption in the first place. This circular dependency limited practical use of symmetric cryptography until asymmetric cryptography solved it.
When to use symmetric cryptography: encrypting data at rest (database encryption, disk encryption), encrypting data in transit after a key has been established (the bulk encryption phase of TLS), and any scenario where both parties already share a secret.
2. Asymmetric Cryptography
Asymmetric (public-key) cryptography breaks the key distribution problem by using a mathematically related pair of keys: a public key that can be shared openly and a private key that must remain secret. The mathematical relationship creates two properties:
- Confidentiality — anyone can encrypt data with your public key, but only you can decrypt it with your private key.
- Authentication and non-repudiation — you can sign data with your private key, and anyone with your public key can verify the signature came from you.
These properties rest on mathematical problems that are easy to compute in one direction but computationally infeasible to reverse: factoring the product of two large primes (RSA), computing discrete logarithms over elliptic curves (ECDSA, ECDH), or lattice-based problems (post-quantum algorithms). These are called one-way functions — the forward computation (encryption, signing) is fast, but the reverse (recovering the private key from the public key) would take longer than the age of the universe with current technology. For a first-principles explanation of why each problem is hard, how elliptic curves achieve the same security with smaller keys, and why quantum computers threaten RSA and ECC, see the hardness assumptions behind asymmetric cryptography.
The cost is performance. Asymmetric operations are roughly 100-1000x slower than symmetric operations. This makes asymmetric cryptography impractical for bulk data encryption — but ideal for establishing shared secrets and verifying identity.
3. Certificate Validation and the Chain of Trust
Asymmetric cryptography proves that someone holds a particular private key. But how do you know the public key you received actually belongs to the server you intended to contact? Without this binding, an attacker could perform a man-in-the-middle attack: intercept your connection, present their own public key, and relay traffic between you and the real server while reading everything.
Digital certificates solve this by having a trusted third party (a Certificate Authority, or CA) vouch for the binding between a public key and an identity. A certificate contains:
- The subject’s identity (domain name, organization)
- The subject’s public key
- The issuing CA’s digital signature over these fields
- Validity period and revocation information
Trust is established through a chain: your browser ships with a set of root CA certificates baked in by the OS or browser vendor. A root CA signs intermediate CA certificates, and intermediate CAs sign end-entity (server) certificates. During TLS, the server presents its certificate chain, and the client walks the chain back to a trusted root, verifying each signature along the way.
If any link in the chain is broken — the certificate is expired, revoked, or signed by an untrusted CA — the connection is rejected.
3.1 Why a Forged Certificate Fails
An attacker can construct a fake certificate containing their own public key and the victim’s domain name. But they cannot produce a valid CA signature on this fake certificate — that would require the CA’s private key, which only the CA possesses. When the client verifies the certificate chain, it recomputes the hash of the certificate’s contents and checks it against the signature using the CA’s public key. The forged signature fails this check because it was not produced by the corresponding private key. This is the mathematical unforgeability property of digital signatures — producing a valid signature without the private key requires solving a computationally infeasible problem (factoring for RSA, discrete logarithm for ECDSA).
4. The TLS 1.3 Handshake
TLS is the protocol that combines all of the above into a practical secure channel. Understanding the exact message exchange reveals why the protocol is designed the way it is.
The goal of the handshake is threefold:
- Authenticate the server
- Negotiate a shared symmetric key
- Do both without an eavesdropper being able to derive the key TLS 1.3 accomplishes this in a single round trip.
- ClientHello: The client sends its supported cipher suites, a random nonce, and critically, a key share — the client’s half of an ephemeral Diffie-Hellman key exchange. Diffie-Hellman is a protocol that allows two parties to derive a shared secret over a public channel without ever transmitting the secret itself. Each side contributes a public value; combined with their own private value, both sides independently compute the same shared secret. An eavesdropper who sees both public values cannot derive the shared secret. In TLS 1.2, the client waited to learn the server’s choice before sending key material. TLS 1.3 speculatively sends it upfront, which is what enables the single-round-trip handshake.
- ServerHello: The server selects a cipher suite, sends its own random nonce and its half of the Diffie-Hellman key share. At this point, both sides have enough information to compute the same shared secret using Elliptic Curve Diffie-Hellman (ECDHE). No private keys were transmitted — only ephemeral public values that are useless without the corresponding private values.
- EncryptedExtensions: From this message onward, everything is encrypted with keys derived from the shared secret. This message contains non-cryptographic server extensions.
- Certificate: The server sends its certificate chain. The client will validate this chain against its trusted root CAs to confirm the server’s identity.
- CertificateVerify: The server signs a hash of the entire handshake transcript so far using its private key. This is the critical authentication step — it proves the server actually holds the private key corresponding to the certificate’s public key. An attacker who intercepted and replayed a legitimate certificate could not produce this signature.
- Finished (server): A Message Authentication Code (MAC) over the entire handshake transcript, using keys derived from the shared secret. A MAC is a keyed hash — it proves both that the handshake was not tampered with and that the sender possesses the shared key.
- Finished (client): The client sends its own MAC. The handshake is complete.
4.1 Why This Design Matters
- Forward secrecy: Because the Diffie-Hellman keys are ephemeral (generated fresh for each connection), compromising the server’s long-term private key does not allow decryption of past sessions. Each session’s symmetric key is derived from a unique DH exchange that no longer exists in memory.
- Hybrid approach: Asymmetric cryptography (ECDHE + certificate signatures) is used only during the handshake for key agreement and authentication. All bulk data encryption uses the fast symmetric keys derived from the handshake. This gives the security of public-key crypto with the performance of symmetric crypto.
- Single round trip: TLS 1.3 reduced the handshake from two round trips (in TLS 1.2) to one by having the client speculatively send its key share in the ClientHello. This matters at scale — shaving one RTT off every new connection is significant when your service handles millions of connections per second.
5. Why HTTP is Stateless and What That Means for Authentication
HTTP was designed as a stateless request-response protocol. Each request is independent — the server does not inherently remember anything about previous requests from the same client. There is no built-in concept of a “session” or “logged-in user” at the protocol level.
This was a deliberate design choice. Statelessness makes HTTP servers simple, cacheable, and horizontally scalable. Any server behind a load balancer can handle any request without consulting shared state. But it creates a fundamental problem for authentication: after a user proves their identity (by submitting a password, for example), how does the server know that subsequent requests come from the same authenticated user?
Every authentication mechanism is, at its core, a strategy for layering identity on top of a stateless protocol. The approaches differ in where the identity state lives — on the server (sessions), in the token itself (JWT), or at a third-party provider (OAuth/SSO).
6. Session-Based Authentication
Session-based authentication is the oldest and most straightforward solution. The server maintains identity state, and the client carries an opaque reference to it.
6.1 How It Works
- The user submits credentials (username and password) over a TLS-encrypted connection.
- The server verifies the credentials against a password database (where passwords are stored as salted hashes, never plaintext). A salt is a unique random value prepended to each password before hashing, ensuring that identical passwords produce different hashes and defeating precomputed lookup tables (rainbow tables).
- If valid, the server generates a session token — a cryptographically random, unguessable string (typically 128+ bits of entropy from a CSPRNG). A CSPRNG (Cryptographically Secure Pseudorandom Number Generator) produces output that is computationally indistinguishable from true randomness — unlike a regular
rand()function, its output cannot be predicted even by an attacker who observes previous values. This token is the only thing that separates an authenticated request from an unauthenticated one, so it must be impossible to guess or predict. - The server stores the session token in a session store (an in-memory hash map, Redis, or a database), mapped to the user’s identity and metadata (user ID, roles, expiration time).
- The server sends the session token to the client via a
Set-Cookieheader withHttpOnly,Secure, andSameSiteflags. - On every subsequent request, the browser automatically includes the cookie. The server looks up the session token in the session store, retrieves the user’s identity, and authorizes the request.
6.2 Security Properties of Session Tokens
The security of the entire system rests on the session token being unguessable. If an attacker can predict or steal a valid session token, they can impersonate the user. Key properties:
- Cryptographic randomness: Tokens must be generated using a cryptographically secure pseudorandom number generator (CSPRNG), not a predictable seed like a timestamp or sequential counter.
- Sufficient length: At least 128 bits of entropy. A shorter token can be brute-forced.
- Server-side storage: The token itself carries no meaning — it is just a random string. All identity information lives in the session store. This means a stolen token reveals nothing about the user until it is presented to the server.
- Cookie security flags:
HttpOnlyprevents JavaScript access (mitigating XSS-based theft),Secureensures the cookie is only sent over HTTPS, andSameSite=StrictorLaxmitigates CSRF attacks.
6.3 The Scalability Problem
Session-based authentication introduces server-side state, and that state must be accessible to every server handling requests. In a single-server deployment, this is trivial — the sessions live in memory. But in a distributed system with many application servers behind a load balancer, every server needs access to the same session store.
Approach 1: Centralized session store (Redis/Memcached). All servers read from and write to a shared session store. This works but introduces a dependency: the session store becomes a single point of failure, and every authenticated request incurs a network hop to look up the session. At high scale, this store must itself be clustered and replicated.
Approach 2: Sticky sessions. The load balancer routes all requests from a given client to the same backend server, which keeps sessions in local memory. This avoids the shared store but creates new problems: if that server goes down, all sessions on it are lost. Load distribution becomes uneven. Rolling deployments become painful because draining a server means logging out its users. Sticky sessions are a workaround, not a solution — they trade one problem (shared state) for several others (availability, uneven load, deployment friction).
Approach 3: Database-backed sessions. Sessions are stored in the primary database. This provides durability and works with any number of servers, but adds latency to every request and increases database load. Often combined with a Redis cache in front.
The fundamental tension is that sessions are server-side state, and server-side state is the enemy of horizontal scalability. This is the motivation for token-based authentication.
6.4 CSRF Protection
Because browsers automatically attach cookies to every request to the domain (including requests initiated by malicious third-party pages), session-based auth is vulnerable to Cross-Site Request Forgery (CSRF). The server includes a unique, per-session CSRF token in forms and validates it on submission. This works because the attacker’s page cannot read the CSRF token from the legitimate page (same-origin policy), so they cannot forge a valid request.
7. JSON Web Tokens (JWT)
JWTs take a fundamentally different approach: instead of storing identity on the server and giving the client an opaque reference, the server encodes the user’s identity into the token itself and signs it cryptographically. Any server that possesses the verification key can validate the token without contacting a session store or the issuing server.
7.1 The Three-Part Structure
A JWT is three Base64url-encoded segments separated by dots: header.payload.signature.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNjgwMDAwMDAwfQ.signature_bytes_here
Header: Metadata about the token — the signing algorithm (RS256, HS256, etc.) and token type (JWT). Base64url-encoded JSON.
{"alg": "RS256", "typ": "JWT"}Payload: The claims — assertions about the user’s identity and the token’s validity. Standard claims include sub (subject/user ID), exp (expiration time), iat (issued at), and iss (issuer). Custom claims like role or permissions can be added.
{"sub": "user-12345", "role": "admin", "exp": 1680000000, "iss": "auth.example.com"}Signature: A cryptographic signature over base64url(header) + "." + base64url(payload), computed using the algorithm specified in the header and a secret key.
- For HMAC-based signing (HS256):
HMAC-SHA256(secret, header.payload)— the same secret signs and verifies, so it must be shared between the issuer and all verifiers. - For RSA-based signing (RS256):
RSA-SHA256(private_key, header.payload)— the issuer signs with a private key, and any verifier can validate with the corresponding public key.
7.2 Why the Signature Proves Authenticity
The signature is unforgeable because producing it requires the private key, and recovering the private key from the public key requires solving a computationally infeasible mathematical problem (factoring for RSA, discrete logarithm for ECDSA). An attacker who modifies the JWT payload (e.g., changing "role": "user" to "role": "admin") cannot recompute a valid signature because they do not have the private key. The verifier detects the tampering because the signature no longer matches the modified payload. For a deeper explanation of why digital signatures are unforgeable, see Cryptographic-Primitives#7. Digital Signatures.
7.3 Why Base64url Encoding (Not Encryption)
A critical point: the header and payload are encoded, not encrypted. Anyone who intercepts a JWT can decode it and read the claims. Base64url is just a URL-safe text representation of binary JSON data — it provides no confidentiality whatsoever.
This is by design. The purpose of a JWT is not to hide information — it is to prove that the claims are authentic and untampered. The signature ensures integrity and authenticity. Confidentiality is provided by the transport layer (TLS).
This means you must never put sensitive information (passwords, credit card numbers, PII that shouldn’t be visible to the client) in a JWT payload.
7.4 The Key Insight: Stateless Verification
The central insight of JWT is that signature verification replaces session lookup. A resource server receiving a JWT does not need to contact the auth server or a session store. It simply:
- Decodes the header to learn the signing algorithm.
- Verifies the signature using the public key (for asymmetric signing) or shared secret (for symmetric signing).
- Checks the claims: is the token expired? Is the issuer trusted? Does the audience match?
- If all checks pass, the user’s identity is right there in the payload.
This is what makes JWT authentication inherently stateless and horizontally scalable. Every service in a microservices architecture can independently verify tokens if it has the public key, with zero cross-service network calls for authentication.
7.5 The Access Token + Refresh Token Flow
Short-lived access tokens (5-15 minutes) limit the damage window if a token is stolen. But forcing users to log in every 15 minutes would be terrible UX. Refresh tokens solve this.
- Access token: A short-lived JWT sent with every API request. Contains the user’s identity and permissions. Verified statelessly by resource servers.
- Refresh token: A long-lived opaque token (30 days to 90 days) stored securely on the client. Used only to obtain new access tokens from the auth server. The auth server stores refresh tokens in a database so they can be revoked.
The flow:
- User logs in. Auth server issues an access token (JWT, 15-minute expiry) and a refresh token (opaque, 30-day expiry). The refresh token is stored server-side.
- Client includes the access token in API requests. Resource servers verify it statelessly.
- When the access token expires, the client sends the refresh token to the auth server’s
/refreshendpoint. - The auth server validates the refresh token against its database. If valid, it issues a new access token (and optionally rotates the refresh token).
- If the refresh token is expired, revoked, or invalid, the user must log in again.
Why this split matters: Access tokens are stateless and fast but irrevocable — if you issue a 15-minute token, you cannot revoke it before it expires (without maintaining a revocation list, which defeats the purpose of statelessness). Refresh tokens are stateful and revocable — the auth server can delete them from the database to force logout. The short access token lifetime limits how long a revoked user retains access.
7.6 The Revocation Problem
This is the fundamental tradeoff of JWT-based auth. Once a JWT is issued, it is valid until it expires. If you need to immediately revoke a user’s access (account compromise, permission change, termination), you have limited options:
- Short expiration times: Limits the window but does not provide instant revocation.
- Token blocklist: The auth server maintains a set of revoked token IDs (the
jticlaim). Resource servers check this list on each request. This works but reintroduces server-side state — the very thing JWTs were designed to avoid. - Revoke the refresh token: The user’s current access token remains valid until it expires, but they cannot get a new one. With 15-minute tokens, this caps the exposure window.
In practice, most systems accept a small revocation delay (equal to the access token lifetime) as an acceptable tradeoff for the scalability benefits of stateless verification.
8. Session-Based vs. Token-Based Authentication
| Dimension | Session-Based | Token-Based #40;JWT#41; |
|---|---|---|
| Where identity lives | Server-side session store | Inside the token itself |
| Verification cost | Network hop to session store per request | CPU-only: signature verification, no network hop |
| Horizontal scaling | Requires shared session store or sticky sessions | Any server with the public key can verify |
| Revocation | Instant: delete the session | Delayed: token valid until expiry |
| Storage | Server bears storage cost | Client bears storage cost |
| CSRF vulnerability | Yes, cookies auto-attached | No, if token sent via Authorization header |
| XSS vulnerability | Cookie with HttpOnly is safe from JS | Token in localStorage is vulnerable to XSS |
| Best for | Traditional web apps, small-medium scale | Microservices, APIs, mobile clients, large-scale |
The choice is fundamentally about where you want the complexity: centralized state management (sessions) or distributed token verification (JWT). Most large-scale systems use JWT for service-to-service auth and API access, while using sessions for browser-facing applications where instant revocation and CSRF protection matter.
9. OAuth 2.0
OAuth 2.0 is an authorization framework (not authentication). It solves a specific problem: how can a user grant a third-party application limited access to their data on another service, without sharing their password?
Consider: a fitness app wants to read your Google Calendar to schedule workouts. Without OAuth, you would have to give the fitness app your Google password. This is terrible — the app could read your email, change your password, or do anything else. OAuth lets you grant the fitness app access to only your calendar, with no password sharing.
9.1 Key Roles
- Resource Owner: The user who owns the data (you).
- Client: The application requesting access (the fitness app).
- Authorization Server: Issues tokens after user consent (Google’s OAuth server).
- Resource Server: Hosts the protected data (Google Calendar API).
9.2 The Authorization Code Flow
The authorization code flow is the most secure and widely used OAuth flow. Understanding why it has two steps (code, then token exchange) is critical.
Step 1 — Authorization request: The client redirects the user’s browser to the authorization server with parameters: client_id (identifies the app), redirect_uri (where to send the user back), scope (what access is requested, e.g., calendar.read), response_type=code, and state (a random value to prevent CSRF).
Step 2 — User consent: The authorization server authenticates the user (login page) and shows a consent screen: “Fitness App wants to read your calendar. Allow?” The user approves or denies.
Step 3 — Authorization code: The authorization server redirects the user’s browser back to the client’s redirect_uri with a short-lived authorization code in the URL query string. This code is not an access token — it is a one-time-use code that proves the user consented.
Step 4 — Token exchange: The client’s backend server sends the authorization code to the authorization server’s token endpoint, along with the client_secret. The authorization server validates the code and client credentials, then returns an access token (and optionally a refresh token).
Step 5 — API access: The client uses the access token to call the resource server’s API.
9.3 Why the Code Exchange Exists
This is the most commonly misunderstood aspect of OAuth. Why not just return the access token directly to the browser in Step 3?
The authorization code flow exists to keep the client secret off the browser. The redirect in Step 3 goes through the user’s browser, which means the URL (and anything in it) is visible in browser history, referrer headers, and potentially to malicious JavaScript. If the access token were in this URL, it could be stolen.
Instead, the URL contains only a short-lived, one-time-use authorization code. The actual token exchange happens server-to-server (the client’s backend to the authorization server), where the client_secret authenticates the client. An attacker who intercepts the authorization code cannot exchange it without the client secret.
9.4 PKCE for Public Clients
Mobile apps and single-page applications (SPAs) cannot securely store a client_secret — the code is visible to the user. This makes the standard authorization code flow vulnerable to an attack where a malicious app on the same device intercepts the authorization code via the redirect URI.
Proof Key for Code Exchange (PKCE) mitigates this:
- Before starting the flow, the client generates a random
code_verifier(a high-entropy string). - The client computes
code_challenge = SHA256(code_verifier)and sends thecode_challengein the authorization request. This is safe because SHA-256 is a cryptographic hash function with preimage resistance — an attacker who sees thecode_challengecannot reverse the hash to recover thecode_verifier. - When exchanging the code for a token, the client sends the original
code_verifier. - The authorization server hashes the verifier and confirms it matches the challenge from Step 2.
An attacker who intercepts the authorization code does not have the code_verifier (it was never transmitted through the browser redirect), so they cannot complete the token exchange. PKCE is now recommended for all OAuth clients, not just public ones.
10. API Key Authentication
API keys are the simplest form of machine-to-machine authentication: a long, random string that the client includes in every request (typically as a header or query parameter). The server looks up the key in a database to identify the caller and their permissions.
10.1 When API Keys Are Appropriate
- Service-to-service calls within a trusted network where the overhead of OAuth is unnecessary.
- Third-party API access where you want to identify and rate-limit callers (e.g., Google Maps API, Stripe API).
- Simple automation like CI/CD pipelines, scripts, and bots.
10.2 Limitations
API keys are not suitable for user-facing authentication because:
- No user identity: An API key identifies an application, not a user. It cannot represent “this request is on behalf of user X.”
- No scoping or consent: Unlike OAuth, there is no mechanism for the user to grant limited permissions.
- Revocation granularity: Revoking an API key revokes access for the entire application, not a single user.
- Shared secret problem: The key must be stored by the client, and if compromised, all access is compromised until the key is rotated.
In practice, API keys are often combined with other mechanisms. For example, Stripe uses API keys to identify the merchant and OAuth to let merchants connect their accounts. AWS uses API keys (access key + secret key) with request signing (HMAC-SHA256 over the request body and headers) to prevent replay attacks.
11. Mutual TLS (mTLS) for Service-to-Service Authentication
Standard TLS authenticates the server to the client, but the client is anonymous at the transport layer. In microservices architectures, you need both sides to prove their identity. Mutual TLS (mTLS) extends the TLS handshake so that the client also presents a certificate.
11.1 How mTLS Works
The handshake proceeds like standard TLS, with one addition:
- After the server sends its certificate, it sends a CertificateRequest message asking the client to authenticate.
- The client sends its own certificate and a CertificateVerify signature proving it holds the corresponding private key.
- The server validates the client’s certificate against a trusted CA (typically an internal CA managed by the organization, not a public CA).
Both sides now know each other’s identity at the transport layer, before any application-level authentication occurs.
11.2 Why mTLS for Microservices
In a microservices architecture with hundreds of services, mTLS provides several properties that application-level auth (like JWT) cannot:
- Transport-layer identity: The identity is established before any application code runs. A compromised application cannot forge its identity.
- Encryption by default: All inter-service traffic is encrypted, preventing eavesdropping even within the data center.
- No shared secrets to manage: Each service has its own certificate and private key. There are no API keys or tokens to rotate manually.
- Zero-trust networking: Services are authenticated regardless of network location, which is essential when running in cloud environments where network boundaries are porous.
Service meshes like Istio and Linkerd automate mTLS by injecting sidecar proxies that handle certificate provisioning, rotation, and the mTLS handshake transparently. The application code is entirely unaware of the cryptographic layer.
11.3 mTLS vs. JWT for Service-to-Service Auth
| Dimension | mTLS | JWT |
|---|---|---|
| Identity granularity | Service identity | User or service identity |
| Layer | Transport (L4/L5) | Application (L7) |
| Request-level claims | No — identity is per-connection, not per-request | Yes — each request carries claims |
| Performance | Handshake cost amortized over connection lifetime | Signature verification per request |
| User context propagation | Cannot carry user identity | Can carry user identity in claims |
In practice, production systems often use both: mTLS for service-to-service channel authentication, and JWT for propagating user identity and permissions through the request chain.
12. Single Sign-On (SSO)
SSO allows a user to authenticate once and access multiple applications without logging in again. It centralizes authentication at an Identity Provider (IdP) while individual applications (Service Providers, or SPs) delegate authentication to the IdP.
12.1 How SSO Works
- The user attempts to access Service Provider A (e.g., Google Drive).
- SP A checks for an existing session. Finding none, it redirects the user to the Identity Provider (e.g., Google Identity).
- The IdP authenticates the user (login form, MFA, etc.) and issues a signed authentication token (JWT, SAML assertion, or OIDC token).
- The IdP redirects the user back to SP A with the token.
- SP A validates the token using the IdP’s public key and creates a local session.
- When the user later accesses SP B (e.g., Gmail), SP B redirects to the same IdP. The IdP recognizes the user’s existing session (via its own cookie) and issues a token for SP B without prompting for credentials again.
12.2 SSO Protocols
- SAML 2.0: XML-based, dominant in enterprise environments (Okta, Azure AD). Tokens are verbose but well-understood by corporate IT.
- OpenID Connect (OIDC): Built on top of OAuth 2.0, adds an
id_token(a JWT) that provides authentication. Preferred for modern web and mobile applications. “Login with Google” uses OIDC. - Kerberos: Ticket-based, used within corporate networks (Active Directory). Not suitable for internet-facing applications.
The choice between OIDC and SAML is largely about the environment: OIDC for consumer and API-first systems, SAML for enterprise integrations with legacy IdPs.
Revision Summary
- Symmetric vs. asymmetric cryptography: Symmetric is fast (bulk encryption), asymmetric solves key distribution (handshakes, signatures). Real systems use both: asymmetric for key exchange, symmetric for data.
- TLS 1.3 handshake: ClientHello (with key share) -> ServerHello (with key share) -> encrypted Certificate + CertificateVerify + Finished. Single round trip. Forward secrecy from ephemeral ECDHE.
- HTTP is stateless: Authentication mechanisms layer identity on top of stateless HTTP. Sessions store state server-side; JWTs embed state in the token.
- Session-based auth: Server stores sessions, client carries an opaque cookie. Instant revocation, but requires a centralized session store that becomes a scaling bottleneck.
- JWT auth:
header.payload.signature— Base64url-encoded (not encrypted). Signature verification replaces session lookup. Stateless and scalable, but tokens are irrevocable until expiry. - Access + refresh tokens: Short-lived access tokens (stateless, irrevocable) paired with long-lived refresh tokens (stateful, revocable). Balances security with usability.
- OAuth 2.0 authorization code flow: Two-step (code then token exchange) keeps the client secret off the browser. PKCE extends this to public clients.
- API keys: Simple, suitable for machine-to-machine. No user identity, no scoping, no consent flow.
- mTLS: Transport-layer mutual authentication for microservices. Proves service identity before application code runs. Often combined with JWT for user context propagation.
- SSO: Centralized authentication at an IdP. OIDC for modern systems, SAML for enterprise.
- Cryptographic primitives (hash functions, Diffie-Hellman key exchange, digital signatures, MACs) are the building blocks underlying all mechanisms in this note. See Cryptographic-Primitives for first-principles foundations.
Deep Understanding Questions
-
A JWT access token was issued 2 minutes ago with a 15-minute expiry. The user’s account is compromised and an admin revokes access. What happens for the next 13 minutes? What architectural options exist to close this gap, and what does each cost in terms of the statelessness property? Ans:
-
In the OAuth authorization code flow, an attacker intercepts the authorization code from the redirect URI. The client does not use PKCE and the client is a public app (no client secret). How does the attacker obtain an access token? How does PKCE prevent this even without a client secret? Ans:
-
If you store JWTs in
localStorageinstead of HttpOnly cookies, you gain immunity to CSRF. But what new attack vector have you introduced? How would an attacker exploit it, and what is the mitigation? Ans: -
In a microservices architecture using mTLS for service authentication, a service needs to make a request on behalf of a user. mTLS proves the service’s identity, but how does the downstream service know which user the request is for? What pattern solves this, and what prevents a compromised service from forging user identity? Ans:
-
A system uses session-based auth with Redis as the session store. Redis is deployed as a single-node instance. What happens when Redis goes down? What happens if you switch to a Redis cluster and a network partition splits the cluster? Can a user appear both logged in and logged out simultaneously? Ans:
-
During the TLS 1.3 handshake, the client sends its key share speculatively in the ClientHello. What happens if the server does not support the key exchange group the client chose? How does TLS 1.3 handle this without falling back to a two-round-trip handshake in the common case? Ans:
-
An organization uses SSO with OIDC. A user logs into Service A, then the IdP experiences a complete outage. Can the user still access Service A? Can they access Service B for the first time? What determines the answers to these two questions? Ans:
-
You sign JWTs with HS256 (HMAC-SHA256, symmetric). Service A issues tokens and Services B, C, and D verify them. What is the security implication of all four services sharing the same secret? How does switching to RS256 (RSA-SHA256, asymmetric) change the trust model? Ans:
-
In the TLS handshake, the CertificateVerify message signs the entire handshake transcript. Why the transcript and not just the certificate? What specific attack does this prevent? Ans:
-
A system uses OAuth 2.0 with refresh token rotation (each refresh issues a new refresh token and invalidates the old one). If an attacker steals and uses a refresh token before the legitimate client does, what happens when the legitimate client tries to use the now-invalidated old token? How should the authorization server respond, and why? Ans:
-
If a quantum computer breaks the elliptic curve discrete logarithm problem, which components of the TLS 1.3 handshake are compromised? Which still hold? What specific changes would be needed, and why can’t you simply increase the ECC key size to resist quantum attacks? Ans:
-
Explain why HMAC uses a nested hash construction (
hash(key XOR opad || hash(key XOR ipad || message))) rather than simplyhash(key || message). What specific attack does the nested construction prevent, and how would an attacker exploit the naive construction? Ans:
Discussion
Comments are open. Anonymous is fine — pick any name and post. Comments appear after a quick moderation check.