SPA Necromancy

Jeroen Techniek

“SPAs are dead” is the kind of (clickbaity?) titles we’re starting to see from prominent security experts. What’s going on? Why are OAuth2 and OpenID for SPA’s under pressure? I’ll give you a rundown…

“SPAs are dead!?” is the title of a recent post by an expert on OAuth2/OpenID. Admitting that the title feels like clickbait, they still claim there’s a core of truth. And I would agree. So what can you do?

The problem

Suppose you have the following setup:

  1. An SPA on app.jeroenheijmans.nl
  2. An API on my-api.jeroenheijmans.nl
  3. Your OAuth2 + OIDC server on ids.infi.nl

The SPA login flows (previously “Implicit” flow, since 2020 “Code+PKCE” flow) will follow this (simplified) set of steps:

  1. User lands at app.jeroenheijmans.nl (not logged in)
  2. App sends user/browser to ids.infi.nl (to log in)
  3. User follows IDS login screens and establishes a cookie-based session
  4. IDS sends user/browser back to app.jeroenheijmans.nl with info in the query string
  5. App extracts query string information to get a bearer token (specifics depend on the flow)

So far, so good! But bad stuff is about to happen, in various way. Here’s one way problems may occur:

  1. User closes the browser tab
  2. A few minutes later they open app.jeroenheijmans.nl in a tab again
  3. The application opens ids.infi.nl in a hidden iframe to do prompt=none login
  4. The iframe should send the cookie for ids.infi.nl to the server so it can see the user is logged in
  5. IDS sends iframe back to a special SPA page with info in the query string
  6. App extracts query string information to get a bearer token (if any)

Things break at step 4. Starting with Safari and Brave, the browser will refuse to send along the cookie, because the iframe is for another domain (infi.nl) then where the SPA is hosted (jeroenheijmans.nl). Chrome announced they will follow suit.

Other similar things will break too: session notifications, SPA logout notifications, and periodic silent refreshes via the iframe technique.

The solutions

Time for some Necromancy! Let’s raise the dead. Surely we can still use SPA’s and resurrect them?

Here’s a shortlist of prominent current solutions.

1. Change DNS to serve IDS on SPA subdomains
Host your IDS on the same domain as your SPA’s. This effectively makes all cookies first party. For example, an SPA on app.jeroenheijmans.nl would need the IDS to be available via ids.jeroenheijmans.nl. The app can still be hosted at infi.nl’s servers of course, just that another DNS record would point to it.

This only works if you’re in control of the DNS records involved. Also, you either need control over hosting of your IDS, or you need a SAAS provider that allows this (e.g. Auth0 allows custom domains).

This is a viable solutionif you are in control of things like DNS, and if you don’t need Single Sign On preserved across various SPA domains.

2. Proxy the IDS via SPA subdomains
Similar to the previous option, you can try to effectively make cookies first party, by creating a proxy for the IDS. That proxy would be hosted on a subdomain sibling to the app.

This seems like a rather impactful workaround, possibly fraught with edge cases. Try it only if the other options really don’t work for you.

The next option is also a “proxy” of sorts, but with some added benefits.

3. Backend-For-Frontend (“BFF”) architecture
This is a lot like the previous option, except that you now go “all in” for the proxying option, and get the added benefits. You create a Backend specifically For your Frontend. That back-end handles communication with the IDS via access tokens. The back-end can have a Secure, SameSite cookie-based session for communication with the front-end.

The blog post from Dominick Baier about this architecture explains it in a lot of detail. Although this BFF option takes a bit of extra work, it does give you a lot of control, and arguably a more secure setup.

4. New browser APIs
Webkit introduced the “Storage Access API” in 2018, and in their post on “blocking 3rd party cookies” they hail it as a solution. It involves explicitly asking the user’s consent to exchange cookies.

Apart from potential UX nuisances, MDN calls this still “experimental technology”. Only Safari and Firefox support it, Chrome, Edge, and Opera don’t.

I guess you could browser-sniff? Then again, Chrome’s starting to introduce cookie-blocking measures, without support for Storage Access API so far. So this option seems no good?

5. Refresh tokens
With the Authorization Code + PKCE flow, refresh tokens seem slightly more reasonable in SPA’s again. So you could consider requesting offline scope, and periodically get a fresh access token without user interaction.

This doesn’t help much with signing a user in automatically when they first load the app. Their session at the IDS might well be valid for 24 hours or more, the refresh token should not be valid for that long. This means at the start of the SPA, you may still need to redirect the user via IDS back to the SPA just to see if they’re logged in. A small visual nuisance, and a moderate delay in starting the app in the first place.

It also solves nothing for logout notifications and the like. Plus it may require additional mitigations (e.g. refresh token rotation), since refresh tokens are so powerful.

6. Other solutions
I’m pretty sure that my view is limited and other solutions are already available. Furthermore, as time goes by and devs have to deal with these issues, even more solutions will be conceived. And finally, there are possibly very different architectures and authentication mechanisms available that don’t suffer from mentioned problems.

In short: don’t stop looking here! Let me know what solutions you know of or come up with!

These Problems are Good

As a web developer, I’m super annoyed. I want to deliver value to users, not reinvent login mechanisms that seemed to work just fine.

As a web user, I’m super pleased! The browser vendors might be forcing devs to rethink authentication, they are also preventing all sorts of nasty privacy-invading tracking mechanisms.

So just as an intermezzo: these changes are for the greater good of things!

In Conclusion

OAuth2, OpenID Connect, and Bearer Tokens (in spite of their faults and complexity) have served me well in the past years. I’m sure it will continue to serve us well in the future. We just need to rethink parts of it.

Allow your SPA authentication to die. Then use your Necromancy skills and resurrect! Surely the world can use a horde of improved SPA’s!?

Een afspraak maken bij ons op kantoor of wil je even iemand spreken? Stuur ons een mail of geef een belletje.