Sign Up
Hero

OAuth2 Implicit Grant and SPA

Everything you always wanted to know (but were afraid to ask)

The OAuth2 working group published a new general security best current practices document which recommends a new approach for using OAuth2 to invoke API from JavaScript in Single Page Applications (SPAs). Namely, it suggests to use the authorization code grant with Proof Key for Code Exchange (PKCE) to request access tokens from SPAs, as opposed to the original OAuth2 spec proposing use of the implicit grant for that scenario.

The new guidance does not stem from any newly discovered vulnerability: if you are satisfied with the threat model of your SPAs based on current guidance, you have no new reasons to update. That said, the new guidance does confer significant advantages: it is strongly recommended that you consider it, especially for brand new applications.

If you choose this new approach, the Auth0 endpoints support all the features you need to start using the new approach today.

A More Detailed Summary

The original OAuth2 specification introduces the implicit grant in SPAs as the way JavaScript code can obtain access tokens and call APIs directly from a browser. Returning access tokens in a URL (the technique used by the implicit grant for SPAs) is fraught by known systemic issues requiring explicit mitigation. However, given the state of browser and web technologies when the grant was first introduced, that was also the only game in town for the scenario.

The OAuth2 working group determined it's time to recommend a different grant to obtain access tokens from SPAs — specifically the authorization code grant for public clients with PKCE. Main drivers appear to have been the ubiquity of CORS and the emergence of sender-constrained technologies.

The new recommendation imposes more requirements on the authorization server, but it doesn't suffer from the issues inherent in returning access tokens in a URL. That results in less mitigation logic required by the client.

How does this impact developers like yourself?

  • If you are building a new SPA, you should consider implementing the new guidance based on authorization code with PKCE. More details below.
  • If you already have SPA apps in your portfolio, they are likely based on the implicit flow — and almost certainly already take steps to mitigate the known issues the approach entails. It is up to you to decide whether you are still satisfied with the mitigations you already have in place, or if it's worth it to update your code to adhere to the new recommendations.

If you want to implement the new recommendations, you'll need to verify whether the authorization server you are using in your apps support the features required to correctly implement the authorization code grant from JavaScript.

If you are using Auth0:

  • The existing Auth0 JavaScript SDKs are based on the traditional guidance. Please ensure you are implementing the mitigations that are appropriate for your scenario. More about this later in the post.
  • The Auth0 endpoints support all the features required to implement the new recommendation: you can find sample code later in this post
  • Support for this new approach is now available through our new Auth0 Single Page App SDK.

That's the short version! The rest of the post will greatly expand on that, helping you to understand the problems the new approach is poised to solve, what concrete options you have available today and what might be coming.

"Auth0 Principal Architect @vibronet on the implication of new OAuth2 security BCP for implicit grant and SPAs."

Tweet This

Keeping things in perspective

Some of you might find that the above summary (and this post in general) doesn't convey the urgency and the alarmed tone you might have noticed in other discussions calling for the immediate cessation of any use of the implicit flow in general. That might have given you a bit of cognitive whiplash, considering that the shortcomings of that grant have been known since 2012 and a very large number of SPAs currently in production, including very prominent products widely adopted and being used every day without major disruptions.

Often those discussions feature well-intentioned generalizations, borne out of necessity to keep communications concise and the discussion accessible to the non-initiated. Add to that the fact that, as trusted security experts, we tend to favor the better-safe-than-sorry mantra, and you'll get advice that often lacks nuance. It's a bit like recommending you to always go buy your groceries in a tank: we'll feel confident we gave you advice that will keep you secure, but you are left to figure out how to exchange your Prius for a tank, how to find parking for it, etc… and if you don't live in a war zone, perhaps the actual risk doesn't justify the investment.

The challenge is that when it comes to standards and security, often deciding whether you live in a war zone or not comes down to reading long specs, paying attention to language with lawyer-like focus. In this post we'll try to spare you some of that and equip you with actionable information, so that you can decide for yourself if and when to do your investment. But of course, when in doubt, if you can afford it, a tank is a pretty cool ride.

The Implicit Grant

Broad statements indicating the deprecation of the implicit grant as a whole are overgeneralizations. OAuth2 defines the implicit grant as pretty much any flow that will result in the authorization server (AS from now on) issuing a token directly from the authorization endpoint, as opposed to issuing it from the token endpoint. The issue tackled by the new guidance focuses on some specific uses of the implicit grant in the context of SPAs, namely the ones in which access tokens are returned in a URL. Other implicit grant uses, such as the ones in which tokens are sent to a server via POST and/or the ones in which the tokens returned are idtokens, are unaffected. You can merrily keep POSTing idtokens to your middle tier for the purpose of signing in with your postback based web apps, and you can keep retrieving id_tokens for your SPA via implicit grant if you are going to consume them in your JS code.

Implicit grant and SPAs

In the case of single page applications calling 3rd party APIs, the client is the JavaScript code running in the browser and the required artifact is the access token required to gain access to the API. Here's a quick diagram for you.

Figure 1: Getting an access token in a SPA via implicit grant

Following the OAuth2 protocol syntax, the client will craft a request with a response_type indicating that the response should contain an access token, such as token, token id_token and code token id_token (where the combinations containing id_token are common OpenId Connect cases). The client will either redirect the browser to the request URL, or will use a popup to do the same — depending on the tastes of the authors of your SDK of choice.

Here there's a sample request for the tokencase. Please note, all the traces in this post are edited for clarity.

(1) GET https://flosser.auth0.com/authorize
  ?audience=https://flosser.com/api/
  &client_id=ZuGSLz6HjGRA8LMtopHBzcKHhCXFtMk8
  &response_type=token 
  &redirect_uri=http://localhost:3000/
  &scope=openid profile email read:appointments
  &state=LxfKVRpEwa4lnPEvmx9LbbXSRIVUMaju

Feel free to ignore the audience parameter there, an identifier we use in Auth0 to indicate 3rd party resources. It doesn't change the substance of the grant here.

For all the response_type values listed earlier, the default response_mode (the mechanism through which the requested artifact will be returned) is fragment – which means that upon successful authentication and consent, the requested token will be returned directly in an HTTP # fragment. Below you can see an example of successful response.

(2) 302 HTTP/2.0
location: http://localhost:3000/#access_token=eyJ0e[..]ju
set-cookie: auth0=s%3AKn[..]Gs; Path=/; Expires=Thu, 15 Nov 2018 19:01:25 GMT; HttpOnly; Secure
Found. Redirecting to <a href="http://localhost:3000/#access_token=eyJ0e [..] ju">http://localhost:3000/#access_token=eyJ0e ju</a>

Your SDK will expect a response of that form, and will take care of extracting the token and make it available to whatever API calling logic your client contains.

Issues

Returning access tokens through the mechanism just described creates numerous opportunities for attackers, which must be explicitly addressed to reduce the attack surface of your application. Here there's a quick list. For more details, you can refer directly to the best practices doc.

  1. Insufficient redirect URI validation. If your AS of choice doesn't enforce a strict match between the redirect URI requested at runtime and the one previously registered for your client, for example by accepting URLs containing wildcards, an attacker might redirect the response (containing the token you requested!) to a URL they control and harvest your credentials. Note: Auth0 enforces strict matching by default.
  2. Credential leakage by referrer header. Without explicit countermeasures (like setting the Referrer-Policy header), requesting a page right after authentication could leak the access token in the Referer header to whoever controls that page .
  3. Browser history. The fragment with the access token may end up in your browser's history, hence exposed to any attack accessing it .
  4. Token injection. Say that an attacker manages to modify a response and substitute the token coming from the AS with one stolen from elsewhere, for example a token issued for a completely different user. There's no way to detect this happened when using the implicit grant as described.

Apart from the last one, all of the issues listed here have mainstream mitigations. That doesn't mean it's always easy to apply those mitigations, nor that every vendor does so consistently, but they were enough to make the implicit flow a viable mechanism for securing SPAs across the industry for half a decade. Think of this use of the implicit grant in SPAs as a pill for the headache that you can only take with a full stomach, can't be taken with alcohol, but when used correctly, does make the headache go away.

Wouldn't it be great if they'd invent a pill you can just take without worrying about all those extra precautions? That's what the new best practice promises to deliver.

Using the Authorization Code Grant from JavaScript

In a nutshell, the recommendation in the best practice doc is simply to implement SPA clients like we've been implementing native clients: with the authorization code grant.

The idea is that the redirect leg of the flow, the one engaging with the authorization endpoint, would only be used to request and obtain an authorization code, and that that interaction would be protected by PKCE to prevent attackers from messing with the code. Once obtained the authorization code, the JavaScript would then proceed to redeem it with the token endpoint- just like it's done by mobile and desktop clients. That would nicely make issues I, II, and III moot.

Figure 2: getting an access token in a SPA via authorization code grant

The catch? The token endpoint on your AS of choice MUST support CORS for the trick to come together. That's not too exotic a feature, but I believe that at this time not every major vendor supports it yet.

If you want to try it yourself, you can play with the sample that our awesome Jose Romaniello put together for the occasion. You can find the repo here. Please note that the code is meant to demonstrate the grant used in the most straightforward possible way, hence you should consider it as an educational tool rather than production-ready code.

The best way to get a feeling of how different this grant is from the implicit one is to compare and contrast what actually goes on the wire.

Here there's the initial request for an authorization code, leg 1 in the diagram.

GET https://flosser.auth0.com/authorize
  ?audience=https://flosser.com/api/
  &redirect_uri=http://localhost:3000/
  &client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
  &response_type=code
  &scope=openid profile email read:appointments
  &code_challenge=xc3uY4-XMuobNWXzzfEqbYx3rUYBH69_zu4EFQIJH8w
  &code_challenge_method=S256
  &state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI

That looks like a classic authorization code request, doesn't it? Note the code_challenge and code_challenge_method, showing that we are using PKCE.

Assuming successful authentication and consent (not traced here), the response in leg 2 of the diagram is what you'd expect from this grant:

302 HTTP/2.0
location: http://localhost:3000/
  ?code=DiXOZqP_t1eX1CVc
  &state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI
set-cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg; Path=/; Expires=Sun, 06 Jan 2019 19:25:57 GMT; HttpOnly; Secure
Found. Redirecting to <a href="http://localhost:3000/?code=DiXOZqP_t1eX1CVc&amp;state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI">http://localhost:3000/?code=DiXOZqP_t1eX1CVc&amp;state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI</a>

Your JavaScript code picks up the code, and redeems it against the token endpoint. This is the part where the CORS support for the token endpoint becomes necessary. In the trace below, representing leg 3 of the diagram, I purposely left in headers I normally omit for clarity… I want to make sure you have a chance to experience the cognitive dissonance to see a browser user-agent string in a code redemption message. I guess I'll get used to it eventually, but I am still doing a double-take whenever I see it.

POST https://flosser.auth0.com/oauth/token HTTP/2.0
authority: flosser.auth0.com
content-length: 231
origin: http://localhost:3000
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
content-type: application/x-www-form-urlencoded; charset=UTF-8
accept: */*
referer: http://localhost:3000/?code=DiXOZqP_t1eX1CVc&state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI
  audience=https%3A%2F%2Fflosser.com%2Fapi%2F
  &client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
  &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F
  &grant_type=authorization_code
  &code_verifier=Huag6ykQU7SaEYKtmNUeM8txt4HzEIfG
  &code=DiXOZqP_t1eX1CVc

Please note the code verifier here, to close the PKCE check.

Leg 4 of the diagram is just the AS returning the requested token(s).

200 HTTP/2.0
date: Thu, 03 Jan 2019 19:25:58 GMT
content-type: application/json
content-length: 1932

{
  "access_token":"eyJ[..]7GQ",
  "id_token":"eyJ0[..]kH7g",
  "expires_in":86400,
  "token_type":"Bearer"
}

Ta dah! We got back an access token to our JavaScript client, without leaving any trace of it in the browser history or in future referral header, and without exposing ourselves to the risk of routing the token bits to the wrong place via redirects. It's magic!

Wait a minute though, you might say. What about the refresh token? Why isn't it returned along with the other tokens?

I am glad you asked. It's… complicated.

Renewing access tokens

The approach described so far works for the initial access token acquisition, entailing explicit user interaction. Access tokens don't strictly need to, but they often expire after a relatively short time. A best practice creates natural opportunities for enforcing revocation and minimizing exposure. When an access token expires, native apps implemented as public clients don't usually force the end user through another prompt — they use refresh tokens to silently obtain a new access token.

Here comes the fun part. If you search through stackoverflow and blogs, you'll see that one of the reasons why identity experts pushed back when customers proposed use of authorization code in SPA — the risk of leaking refresh tokens. Refresh tokens are powerful artifacts, and in the case of public clients you don't even need to steal a secret to use them: once you get your hands on a refresh token, you can use it right away. The fact that a browser doesn't really offer safe places to save critical artifacts (see all the debacle on the use of storage for access tokens, a far less powerful credential, and the significant risk of XSS attacks dumping them out) didn't help at all, of course.

What changed? Why is it suddenly OK to use refresh tokens in a browser? The answer lies mostly in the emerging sender-constrained tokens technologies. Using token binding or mutual TLS authentication, it is possible to tie a particular refresh token to the client instance it originally requested; that means that an attacker obtaining the bits of a refresh token wouldn't be able to use it. That's the theory, at least: unfortunately neither technologies are widely available in the wild. Token binding suffered an important setback when Chrome walked back its support, and iOS never committed to implementation. The MTLS spec is more promising in term of potential adoption, but it's still early days (it's still a draft being actively worked on) and there are details to iron out (for example, using this technique from a browser might pop out UX unexpectedly). That means that in practice today you can't use sender constrained refresh tokens with most mainstream providers.

Not everything is lost, though. The best practices doc states that another acceptable mechanism for protecting refresh tokens for use in public clients (not just browsers) is refresh token rotation, a feature that invalidates a refresh token and issues a new one whenever it is used to refresh an access token. Security-wise, that's a great feature. In real life that can become a handful: think of what happens if you have multiple client instances sharing a token store (you need to serialize access) or if you successfully use a refresh token but fail to receive a new one. Either way: I personally know of only two products supporting refresh token rotation as of today. Neither Microsoft, Google, nor Auth0 offer it at the moment. [update: since then, Auth0 added support for refresh token rotation! please see https://auth0.com/docs/libraries/auth0-spa-js?ga=2.198231919.1941454603.1594245172-762247280.1581520636#use-rotating-refresh-tokens for details]_

Given that the guidance above is about public clients, not just browsers, it would appear that most current native clients violate it (though that doesn't make them non-compliant with the standard, given that the best practices document doesn't amend the original OAuth2 specification).

What to do? Considering that a browser is a far more dangerous environment than, say, a mobile platform app sandbox, I would recommend that unless (or until) your scenario does offer one of those refresh token protection features, that you do NOT use refresh tokens in your SPAs.

Luckily, there is an alternative to renew access tokens in SPAs without using refresh tokens AND without falling back to implicit. You can leverage the presence of a session cookie with the AS to request a new authorization code without showing any UX via hidden iframe and prompt=none (assuming your AS does OpenId Connect as well). That is the same trick we use in the implicit flow, but the difference is that here we get back a code rather than a token — obtaining the same advantages we did in the interactive version of the flow.

That's what we demonstrate in Jose's sample code. In the trace below you can see a request for a new access token via hidden iframe.

GET https://flosser.auth0.com/authorize
  ?audience=https://flosser.com/api/
  &redirect_uri=http://localhost:3000/
  &client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
  &response_type=code
  &response_mode=web_message
  &prompt=none
  &scope=openid profile email read:appointments
  &code_challenge=WtcXoh6_3hCtvn15TW0VEFrjru2kRZO8kWcFurXMo4Y
  &code_challenge_method=S256
  &state=QIA33u4JKTwCmXjPcZXoHyJD5h5szwFs
cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg

Things to notice:

  • The request looks a lot like the one we observed during the interactive phase, including all the PKCE machinery.
  • One important difference is the prompt=none directive. We want this to happen without any UX.
  • Another fundamental difference is that our request is accompanied by the session cookie, which should prove the user's sign in status without prompts
  • In Auth0 we like web_message as response_mode when communicating with iframes. For the purpose of discussing differences between implicit vs authorization code grants it doesn't make much of a difference.

The response returns the requested code, delivered via JS as expected. The AS also takes the opportunity to update the session cookie.

200 HTTP/2.0
set-cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg; Path=/; Expires=Sun, 06 Jan 2019 20:12:07 GMT; HttpOnly; Secure

<!DOCTYPE html><html><head><title>Authorization Response</title></head><body><script type="text/javascript">(function(window, document) {var targetOrigin = "http://localhost:3000";var webMessageRequest = {};var authorizationResponse = {type: "authorization_response",response: {"code":"a_Yja9QKgxtwCnA2","state":"QIA33u4JKTwCmXjPcZXoHyJD5h5szwFs"}};var mainWin = (window.opener) ? window.opener : window.parent;if (webMessageRequest["web_message_uri"] && webMessageRequest["web_message_target"]) {window.addEventListener("message", function(evt) {if (evt.origin != targetOrigin)return;switch (evt.data.type) {case "relay_response":var messageTargetWindow = evt.source.frames[webMessageRequest["web_message_target"]];if (messageTargetWindow) {messageTargetWindow.postMessage(authorizationResponse, webMessageRequest["web_message_uri"]);window.close();}break;}});mainWin.postMessage({type: "relay_request"}, targetOrigin);} else {mainWin.postMessage(authorizationResponse, targetOrigin);}})(this, this.document);</script></body></html>

From this point onward, the code redemption against the token endpoint plays out just like we described for the interactive portion of the flow.

Hopefully this clarified how you can perform background token renewals without requiring a refresh token in JavaScript. That works pretty well, though there's something that kind of ruins the party for me. The mechanism described here still relies on the iframe to be able to access the session cookie. There are various situations where that access might not be granted, as it is the case in Apple's ITP2 (see discussion here). Using a refresh token would make the issue moot, which is why I believe it is worth it to keep an eye on this space and push for more widespread adoption of the security measures that would make refresh tokens usable from JavaScript. There is an entirely different chapter about what the implications for distributed session termination would be, but this post is long enough as it is already — we'll talk about it in some other installment.

"Heard about the new guidelines for SPAs in the latest OAuth2 security BCP? Learn how to apply all that on your Auth0 apps in this article from @vibronet."

Tweet This

Different SPA Topologies and Alternative Approaches

Throughout the best practice doc, and the post, a key underlying assumption is that the SPA we are considering is in its most canonical form: a set of static HTML and JS files, running all logic in the browser.

In fact, it is very common for SPAs to have a server-side component. Being able to run logic on the server creates the opportunity for more approaches to the problem of securing API calls from JavaScript code running in a browser. Those approaches (and others) will be discussed by the OAuth2 working group in a browser-based apps best current practices document (BCP), currently at its third draft and under active discussion.

Just to give you a taste, let's mention the two most common topologies.

SPAs calling APIs exclusively on its own domain

Figure 3: If your APIs live on the same domain as your SPA bits, you can secure calls via session cookies just like you'd do for traditional web apps

If your SPA pages are coming down from the same domain where you host the API endpoints you want to call, you can happily dispense with all this javascript-requests-uses-and-manage-tokens brouhaha, and simply secure API calls just like you would secure postbacks to traditional web apps: via session cookies. Secure your API just like you would secure a web app for sign in, for example by slapping an OpenID Connect middleware in front of it, and let it issue and validate cookies for you. Your JS doesn't have to contain any explicit logic for securing calls, as the browser will automatically attach the cookies for your app domain (hence the session cookie) to every request, including AJAX calls to API endpoints.

This is my favorite solution: simple and proven. The main challenge for Identity as a Service solutions is how to ask to all developers (including non-experts) whether their app is indeed eligible for this approach, and how to deal with app evolutions changing the situation (e.g. starting to cross -domain APIs too). But that's not a problem for you, the advanced developer braving complicated blog posts like the one you are reading at the moment!

There are at least a couple of extra things that I believe will benefit from extra guidance from the upcoming browser-based BCP:

  • If the API are secured with the same middleware used to enforce web sign on, they are likely to return a 302 when the session cookie expires. 302s aren't really actionable when returned in AJAX calls, and that means that the JS code will need some error management logic to handle the situation- one that possibly doesn't end up sending the browser to pages controlled by an attacker
  • SPA apps often require their JS code to know some user information, say the username to be displayed on the top right corner of their UX. It would be easy to dismiss this as exercise for the reader, giving vague indications like "just expose the info you need in one of your API". That sounds like something that should be properly threat modeled and formalized, so that it can be enshrined in SDKs rather than forcing every developer pursuing this approach to reinvent the wheel over and over again.

Apart from the two details above, which will require you to roll your own solution, this approach is available to you TODAY if your app hosts pages and API on the same domain. Also note: just like the next one, this approach has the added bonus of sidestepping any ITP2 related issues.

SPAs using their backend to obtain and forward tokens back to the JS layer

Figure 4: If you have a backend, you can use it to proxy access token requests to the AS… but you become responsible for a critical architectural component, without any protocol guidance to rely on.

Another possible use of a backend to solve this scenario is to delegate all token acquisition responsibilities to the server side portion of the code. That would sidestep all the issues discussed so far in using JavaScript to run the token acquisition logic. That sounds awesome in theory, but there are a couple of pitfalls one should consider before choosing this approach.

  • Your backend would need to pass the tokens it obtains back to the JS code, given that it's the layer that needs to perform the API calls. The backend would also be responsible to renew those tokens when they expire. See where I am going? Your backend would turn into an authorization server of sort for your JS code, but one that isn't described in any specification or public threat model. You'd be responsible for a critical component of your architecture, without the benefit of following a design that has been validated by the community, or without the chance to using off the shelf components implementing the feature. This is another aspect that I believe would benefit from more prescriptive guidance.
  • Tokens obtained by the backend would be marked as issued to a confidential client, but the actual client's security characteristics would be the one of the JS layer, perhaps the most vulnerable of all public clients. Whatever API you are calling using this mechanism should NOT use the client type as a decision factor for authorization. Alternatively, the AS should provide confidential clients with a mechanism to signal that the requested token will not be used by them, so that the token bits can reflect it accordingly. And of course, sender constrained mechanisms would need to be updated to make this request by proxy possible. More food for thought for the working group.

Just like the other alternative approach, this solution can be implemented TODAY if you do have a backend and you are willing to roll your own solution to the two points above.

I do have to stress the need for caution here. The principle "the client requesting the access token is the same client that will use the token to access the resource" is pretty fundamental, and violations might easily lead to unintended consequences. For example, imagine that your AS has logic including sensitive claims only when issuing tokens to confidential client, on the assumption that they will not be directly accessible on the end user's device. The proxy pattern invalidates that assumption, potentially with dire consequences. If you do decide to adopt this pattern, make sure to double and triple check your solution for similar side effects.

Next steps

Did you read all the way here? You are nothing short of a hero! Hopefully your investment paid off and you are now better equipped to make decisions about how to secure your SPA.

To put a bow on all this, here's a summary of the recommendations.

Short term

  • Track down your existing SPAs. Consider whether you are happy with your existing mitigations and want to stick with what you have (likely implicit) for the time being, or if the advantages of the new approach are worth the cost of an update. (Remember the headache pill analogy earlier.).
  • If you are about to develop a new SPA, or decide to update an existing one, consider if your provider supports new approach and to what extent. Things to consider:
    • Do they support CORS on the token endpoint? (Auth0 does)
    • Do they support sender constrained tokens tech? (Almost no one does AFAIK.)
    • Do they support refresh token rotation? (Very few do.)
    • Do they offer an SDK implementing authorization code grant in JS? (Auth0 doesn't yet, but we will… in the meanwhile, we have a sample.)
  • If your SPA has a backend, consider whether the alternative approaches described here are right for you

Medium term

  • Keep an eye on updates here.
  • Watch for Auth0 announcements about a new SDK release supporting the approach.

Happy coding!

About Auth0

Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.