Web security patterns are a lot like fashion trends. What looked sharp ten years ago now feels outdated, and the styles everyone thought would last forever sometimes vanish in a season. Cookies, tokens, proxies, they’ve all had their runway moment.
Let’s take a light-hearted but practical tour of how browser security has evolved - from the early days of cookies, to the heyday of SPAs, to the modern approaches.
The oldest and most battle-tested approach. Server-rendered apps relied heavily on cookies for session management, long before OAuth and SPAs came along. While simple and effective, cookies had to evolve with new browser features to keep up with modern threats.
📅 Era: Early web apps (pre-SPA, server-rendered pages).
🤔 How it works:
✅ Pros:
❌ Cons:
💪 Use Case:
localStorage or sessionStorage.
→ If a token is in localStorage and your app suffers an XSS, an attacker can simply read it. With HttpOnly cookies, the attacker can’t.Strict: Only sent in first-party contexts.Lax: Sent for top-level navigations (good balance).None + Secure: Required for third-party usage.As SPAs emerged, developers needed a way to authenticate without a backend. The Implicit Flow was introduced to give client-side apps direct access to tokens - a clever hack for its time, but one that opened the door to serious security issues.
📅 Era: Early SPAs (~2012).
🤔 How it works:
✅ Pros:
❌ Cons:
💪 Use Case:
To fix the flaws of the Implicit Flow, PKCE (Prook Key for Code Exchange) came into play. This was a big leap forward for SPAs, giving them a safer way to obtain tokens directly. But while more secure, it still left the browser holding sensitive tokens.
📅 Era: Mid SPAs (~2016).
🤔 How it works:
✅ Pros:
❌ Cons:
💪 Use Case:
When applications grew into ecosystems of microservices, authentication had to scale with them. API gateways and reverse proxies emerged as central control points - moving the burden of auth away from the browser and onto infrastructure.
📅 Era: Microservices era.
🤔 How it works:
✅ Pros:
❌ Cons:
💪 Use Case:
The modern favorite. BFFs combine the simplicity of cookies with the security of OAuth. By keeping tokens server-side and giving the browser only safe cookies, BFFs drastically reduce the attack surface for SPAs.
📅 Era: Modern standard (~2020+).
🤔 How it works:
✅ Pros:
❌ Cons:
💪 Use Case:
Even with secure authentication, XSS remains one of the biggest threats to web apps. That’s where CSP comes in. It acts as a browser-enforced guardrail, letting you tightly control which scripts, styles, and resources are allowed to run. Think of it as a whitelist for your frontend - reducing the blast radius if malicious code ever sneaks in.
📅 Era: Introduced in 2012, now a modern standard supported by all major browsers.
🤔 How it works:
Content-Security-Policy HTTP header.✅ Pros:
❌ Cons:
unsafe-inline or unsafe-eval weakens CSP significantly.💪 Use Case:
| Pattern | Era | Tokens in Browser? | Security Strength | Use Case |
| Cookie-Based Auth | Oldest | ❌ No | ⭐⭐ Medium | Monolithic/intranet apps, simple sessions |
| OAuth Implicit Flow | Early SPAs | ✅ Yes | ⭐ Low | Legacy SPAs with no backend |
| Auth Code + PKCE (SPA) | SPAs v2 | ✅ Yes | ⭐⭐ Medium | Serverless SPAs (Netlify, Vercel) |
| API Gateway / Reverse Proxy | Microservices | ❌ No | ⭐⭐⭐ High | Enterprise microservices, multiple frontends |
| Backend-for-Frontend (BFF) | Modern | ❌ No | ⭐⭐⭐⭐ Very High | Secure modern SPAs needing OAuth/OIDC |
| Content Security Policy (CSP) | Always | N/A | ⭐⭐⭐⭐ Very High | All modern apps to mitigate XSS |
Each of these patterns reflects its time and context. Cookies kept things simple, Implicit Flow fueled the first wave of SPAs, PKCE added better protections, gateways helped large-scale systems, and newer approaches combine security with modern frontend needs.
But don’t forget: authentication is only half the story. Pairing modern auth patterns (like BFF with HttpOnly cookies) with defensive browser features like CSP is what makes the difference between “secure enough” and “battle-hardened.”
There’s no universal “best” - the right choice depends on your architecture, risk tolerance, and future plans. Understanding the trade-offs helps you pick a pattern that matches your app’s needs without getting stuck in yesterday’s trend.
There’s no universal “best” - the right choice depends on your architecture, risk tolerance, and future plans. Understanding the trade-offs helps you pick a pattern that matches your app’s needs without getting stuck in yesterday’s trend.