PKCE and the Death of Implicit Flow: How to Secure SPAs & Mobile Apps

Share this

PKCE – The better way to OAuth in the browser-first era
Remember the old days when SPAs used the Implicit Flow to get tokens directly in the browser?

It was quick. It was “stateless.” It was simple.

…But it was also insecure. And in today’s security landscape — that just doesn’t fly anymore.

Enter PKCE — Proof Key for Code Exchange — the modern upgrade to OAuth 2.0 that secures public clients like SPAs and mobile apps without exposing tokens to the browser.
Note: This article belongs to Part 5.1: App Security Best Practices in our Application Security series.

In this post, we’ll explore:

  • Why the Implicit Flow is outdated
  • How PKCE works under the hood
  • Why it’s now the standard even for browser-based apps
  • How to use it properly with Spring Security and modern identity providers
Application Security Series
Application Security Series

What Was the Implicit Flow (And Why Did We Use It)?

The Implicit Flow was designed for public clients (like SPAs) that couldn’t safely store a client secret. Since they couldn’t keep secrets, they were allowed to skip the code exchange step and receive tokens directly from the authorization endpoint.

[SPA] ---> [Auth Server]
<--- access_token in URL fragment

No backend. No code exchange. No refresh tokens.

At the time, it made sense: browsers couldn’t make secure backend calls, and keeping user experience fast was critical.

But…

Why Implicit Flow is Now Deprecated

The Implicit Flow has serious limitations:

  • 🔓 Access token appears in the browser URL
    • It’s exposed to history, logs, extensions, and cross-site leaks
  • 🔁 No refresh token support
    • Requires frequent reauthentication
  • 🧪 Hard to validate securely
    • No client authentication or anti-CSRF protection
  • 🕵️‍♀️ High risk in modern browser environments
    • More XSS risks, more attack vectors

In short: it solved yesterday’s problems, but today it causes more than it prevents.

That’s why the OAuth 2.1 draft deprecates it entirely in favor of Authorization Code flow with PKCE — even for SPAs.

Enter PKCE: Proof Key for Code Exchange

Originally built for mobile apps, PKCE is now the gold standard for all public clients, including SPAs.

💡 What is PKCE?

It’s a simple mechanism that adds a layer of verification to the Authorization Code flow. It protects against authorization code interception attacks by using a one-time secret.

🔐 How PKCE Works (Step-by-Step)

  1. Client (SPA/Mobile) creates:
    • A random string called the code verifier
    • A hash of it called the code challenge
  2. Client initiates login:
    • Sends the code challenge (not the verifier) to the authorization server
  3. User authenticates and the server sends back an authorization code
  4. Client exchanges the code for tokens at the token endpoint:
    • This time, it sends the original code verifier
  5. Server checks if:
    • code_challenge (step 2) matches the hash of the code_verifier (step 4)
    • If it does, issue tokens
[SPA] --(code_challenge)--> [Auth Server]
<-- auth_code --
[SPA] --(auth_code + code_verifier)--> [Token Endpoint]
<-- access_token + refresh_token --

The verifier never leaves the client until the final step. Intercepting the code alone is useless without it.

PKCE vs Implicit Flow: Quick Comparison

FeatureImplicit FlowAuth Code + PKCE
Tokens in browser URL✅ (bad)❌ (good)
Refresh tokens allowed
Susceptible to attacks
Secure on public clients
OAuth 2.1 recommended?❌ Deprecated✅ Preferred

Using PKCE in Practice

Most modern IdPs (Okta, Auth0, Azure AD, Keycloak, etc.) support PKCE by default for public clients.

If you’re building with:

  • React/Vue/Angular → Use libraries like oidc-client, Auth.js, or AppAuth.js
  • Mobile apps → Native SDKs implement PKCE out of the box
  • Spring Boot (BFF) → No change needed — PKCE is handled on the frontend; server just handles code exchange securely

Spring Security & PKCE

If you’re using a BFF setup with Spring Boot, PKCE is handled on the frontend. Your Spring backend acts as a confidential client:

spring:
security:
oauth2:
client:
registration:
your-client:
client-id: xyz
client-secret: secret
redirect-uri: "{baseUrl}/login/oauth2/code/your-client"
scope: openid, profile, offline_access

If you’re building a public OAuth client directly in Spring (e.g., CLI app), you can programmatically enable PKCE via OAuth2AuthorizationRequestResolver.

But in most SPA+BFF setups: the frontend handles PKCE, and Spring just securely completes the code exchange.

Best Practices for SPAs & Mobile Apps

✅ Always use Authorization Code Flow with PKCE
✅ Never use Implicit Flow — it’s deprecated
✅ Store refresh tokens securely (via BFF or HTTP-only cookies)
✅ Avoid storing tokens in localStorage or sessionStorage
✅ Use HTTPS — always
✅ Validate tokens properly on the backend

Conclusion

OAuth has evolved — and PKCE is a major part of that evolution. It brings a lightweight, secure enhancement to the tried-and-tested Authorization Code flow.

For developers working on SPAs, mobile apps, or public clients — this isn’t optional anymore. It’s the new default.

Check out https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow-with-pkce link for more information on the current topic.

What’s Next

Next up in the Application Security series:

👉 Top OAuth/OIDC Misconfigurations and How to Avoid Them

We’ll walk through the most common (and dangerous) missteps teams make when implementing identity — and how to steer clear of them.

Have a Question?

Trying to migrate from Implicit Flow? Need help wiring PKCE into your SPA or BFF stack? Want to validate your Spring Boot OAuth setup? Let us know in the comments section — happy to chat!

Share this

Leave a comment

Your email address will not be published. Required fields are marked *