Authentication Isn't Hard. Authentication Done Correctly Is.
Every developer can build a login page. Give a junior engineer a weekend and a copy of bcrypt and they'll produce something that works: a form, a database check, a session cookie. It'll even be secure enough for a side project. The gap between that and a login system that survives enterprise custome
Every developer can build a login page. Give a junior engineer a weekend and a copy of bcrypt and they'll produce something that works: a form, a database check, a session cookie. It'll even be secure enough for a side project. The gap between that and a login system that survives enterprise customers, penetration tests, federation requirements, and five years of feature requests isn't a gap in effort. It's a gap in the number of failure modes you've been forced to learn about the hard way — usually because someone else already got hacked and wrote it up.
What follows is a partial list of things that look optional right up until they aren't.
Clock skew
Token-based auth leans heavily on timestamps: a JWT's exp claim, a SAML assertion's NotOnOrAfter, a TOTP code's 30-second window. All of it assumes the clocks on both ends roughly agree. In practice they don't — server clocks drift, containers restart with NTP not yet synced, a customer's on-prem IdP is four minutes behind UTC because nobody's touched it since 2019. Validate timestamps with zero tolerance and you'll get intermittent, maddening authentication failures that only happen to some users, some of the time, and are almost impossible to reproduce on demand. Allow unlimited tolerance and you've reopened the replay window the timestamp existed to close. The right answer is a small, deliberate skew allowance — typically measured in seconds, not minutes — and it's the kind of detail that never shows up in a tutorial because tutorials run client and server on the same laptop.
Session fixation
An attacker gets a valid session ID onto a victim's browser — through a link, a shared device, or a subdomain that doesn't scope cookies correctly — then waits for the victim to log in using that same session ID. If your app doesn't issue a fresh session identifier on authentication, the attacker now shares the victim's authenticated session. The fix sounds trivial: rotate the session ID on login. It's trivial precisely because someone already worked out that you have to do it, which is the entire theme of this article.
Replay attacks
Any credential or token that can be captured and reused later is a replay risk — a bearer token sniffed off an unencrypted connection, a SAML assertion intercepted and resent, an API request replayed byte-for-byte. Defending against replay means designing for single-use where it matters (nonces, one-time codes), short expiries where reuse can't be entirely prevented, and TLS everywhere so capture is harder in the first place. None of this is exotic cryptography. It's the unglamorous discipline of assuming every message on the wire will eventually be recorded and reused by someone who shouldn't have it.
PKCE
Authorization code interception is the reason PKCE exists. In a public client — a mobile app, a single-page app — there's no safe place to store a client secret, which means the authorization code itself, sitting briefly in a redirect URI, becomes the thing worth stealing. PKCE (Proof Key for Code Exchange) closes that gap by having the client generate a secret verifier at the start of the flow, send only its hash, and then prove possession of the original verifier when exchanging the code for a token. An intercepted code is useless without the verifier. It's a small addition to the flow that quietly eliminates an entire class of attack that mobile OAuth was vulnerable to for years before PKCE became mandatory practice.
Nonce validation
State prevents CSRF against the authorization endpoint. Nonce is a different, narrower defense: it binds an ID token to a specific authentication request so a token issued for one login attempt can't be replayed into a different session. Skip nonce validation and an attacker who can get their hands on a valid ID token — through a compromised network, a malicious redirect, or a leaked log — may be able to inject it into someone else's session. It's a single string comparison in the code. It's also the difference between OpenID Connect being sound and merely looking sound.
SameSite cookies
For most of the web's history, cookies were sent on every request to their domain regardless of where that request originated, which is exactly what makes CSRF possible: a malicious site can trigger a request to your app, and the browser helpfully attaches the victim's session cookie. SameSite=Lax or Strict tells the browser not to do that for cross-site requests. It sounds like a minor header attribute. It's actually one of the few CSRF mitigations that costs the developer almost nothing and closes almost the entire attack class — provided you set it, which plenty of session cookies still don't.
Key rotation
Every signing key has a lifetime after which it should stop being trusted, whether because of a compliance requirement, a suspected compromise, or simple hygiene. The naive approach — swap the key and immediately invalidate the old one — breaks every token that was signed five minutes before the rotation and hasn't expired yet. The systems that get this right maintain overlapping validity windows, publish multiple active keys via something like a JWKS endpoint, and let old tokens age out naturally instead of dying on the spot. It's a logistics problem disguised as a cryptography problem, and it's usually the thing that gets skipped under deadline pressure, right up until a key needs to be revoked in an emergency and there's no mechanism to do it without an outage.
Refresh token rotation
A static, long-lived refresh token is a permanent credential: steal it once and you have standing access until someone notices. Rotation issues a new refresh token on every use and invalidates the old one, which does two useful things. It limits the blast radius of a single leaked token, and — if implemented with reuse detection — it gives you a signal: if a refresh token that's already been rotated shows up again, that's evidence of a stolen token in play, and the whole token family can be revoked. Getting this right means tracking token lineage, which is more state than most teams expect to have to manage for something that started as "just refresh the access token."
XML Signature Wrapping
If your product supports SAML, this is the one that should worry you most, because it's subtle enough that "we validate the signature" isn't actually sufficient. XML Signature Wrapping attacks exploit the gap between where a signature is computed over in an XML document and where the application actually reads its data from. An attacker can take a validly signed assertion, wrap it inside a forged structure with different content, and if the parser reads from the wrong element, a technically-valid signature ends up authenticating attacker-controlled data. Defending against it requires strict, canonical parsing that ties signature validation to the exact element being trusted — not "is there a valid signature somewhere in this document," but "is this specific assertion, this specific content, signed." It's a class of bug that has quietly affected SSO implementations from major vendors, not just hobby projects.
Logout
Logout looks like the simplest part of the system — clear a cookie, done. It isn't, once federation is involved. Single logout across a SAML or OIDC federation means propagating the logout event to every service the user was signed into, not just the one they clicked "log out" on, and doing it in a way that doesn't depend on the user's browser reliably visiting every relying party. Get it wrong and you get the two failure modes users actually notice: a user who logs out and finds themselves still authenticated somewhere else, or a user whose session dies unexpectedly because a logout propagated somewhere it shouldn't have.
MFA enrollment
The authentication check itself is the easy part of MFA. Enrollment is where the real design problems live: what happens when a user loses their authenticator app, when they're mid-enrollment and abandon the flow, when they need backup codes and have to be told, clearly, that those codes are as sensitive as a password. A well-designed MFA enrollment flow has to account for a user who's locked out through no fault of the system — a broken phone, a reinstalled app, a lost device — without turning that recovery path into the easiest way for an attacker to bypass MFA entirely.
Recovery
Which leads to the hardest problem on this list: account recovery is, structurally, a second authentication system, and it's usually weaker than the first one, because "prove who you are without your normal credentials" is inherently a lower-assurance problem. Every recovery flow — email links, security questions, support-agent overrides — is a potential bypass for every security measure layered on top of primary login. The teams that get this right treat recovery with the same rigor as authentication itself, not as an afterthought bolted on to handle the "I forgot my password" support tickets.
The conclusion
None of this is complexity for its own sake. Every item on this list exists because a simpler, earlier version of the same idea was tried, and it failed in a way specific and costly enough that someone wrote a standard, a CVE, or a postmortem about it. Clock skew tolerance, PKCE, nonce validation, SameSite cookies, key rotation, refresh token rotation, strict XML parsing — these aren't bureaucratic requirements imposed by standards bodies with nothing better to do. They're the accumulated, hard-won knowledge of an entire industry's worth of authentication systems that got broken, encoded into something a competent team can implement without personally rediscovering the failure first.
Authentication complexity isn't accidental. It's accumulated industry knowledge — and the cost of ignoring it isn't that your login page looks unsophisticated. It's that you get to rediscover, on your own production users, exactly why each of these details mattered.