38

A lot of services today still recommend the implicit flow for an OpenID Connect/Oauth2 token exchange when developing Single-Page Apps. (See Okta - now recommends PKCE w/ implicit fallback, Google, Auth0)

Some newer guidance out there points towards using the Authorization Code Flow without a client_secret in the token exchange step, which I can agree makes sense for the reasons cited in the article (e.g. tokens don't live within browser history, web logs, etc.). Why isn't this concept taken a step further with PKCE? Instead of omitting the client_secret completely, why not utilize a dynamic one as it is recommended for native applications? SPAs and Native Apps are both considered "public clients" where a secret cannot be safely kept, but only Native Apps get the PKCE recommendation while SPAs seem to be kept in the past.

I understand the security implications PKCE is trying to solve doesn't directly relate to a browser only context, but couldn't this be viewed as a defense-in-depth mechanism and utilized anyway? I know from experience Google won't let you generate a Web Application Credential and try to use anything but implicit for a SPA (see this issue), the Authorization Code flow will expect a client_secret and the PKCE exchange only works if you choose a native app credential type but then you can't specify https redirect_uri's for your application.

Other providers allow PKCE with Authorization Code flow in a web-context, but it isn't recommended from them. Am I wrong in wanting this and utilizing it? It seems fairly trivial to add the additional steps of generating and passing around the code challenges with the Web Crypto APIs available (targeting newer browsers and shimming as needed).

someone1
  • 686
  • 1
  • 7
  • 10
  • 10
    For all who didn't know what PKCE means (like me), a link to [Proof Key for Code Exchange](https://www.oauth.com/oauth2-servers/pkce/). – martinstoeckli Apr 03 '18 at 21:12
  • Some more reading... "In practice, there are only very limited cases in which this is necessary. Several major implementations (Keycloak, Deutsche Telekom, Smart Health IT) have chosen to avoid the Implicit Flow completely and use the Authorization Code flow instead." https://www.oauth.com/oauth2-servers/single-page-apps/ – whatsthatitspat Oct 09 '18 at 19:41
  • "In the context of browser apps (“single page apps”), the OAuth2 Implicit Flow was designed when most browsers did not allow developers to modify the history via, for example, `pushState`. Back then, using hashtags was the only way to transmit data via URLs to JavaScript applications running in the browser. Today, as browser APIs improved, it’s possible to use the OAuth2 Authorize Code Flow for browser apps instead, and benefit from, e.g., refresh tokens." https://www.ory.sh/oauth2-for-mobile-app-spa-browser/ – whatsthatitspat Oct 09 '18 at 19:42
  • 1
    I'm a contributor to some ory projects (not relevant I guess) and that blog post is a bit misleading - refresh tokens must be kept in a secure and safe location which, according to OWASP, is not currently possible in the browser - thus making refresh tokens unsafe in the setting of a SPA. – someone1 Oct 09 '18 at 20:33
  • @someone1 Would you consider this a possibility if the single-page-app was a Cordova hybrid app, and thus had the ability to store a refresh token [in the keychain](https://github.com/sjhoeksma/cordova-plugin-keychain-touch-id), protected by Touch ID? – JW. Dec 27 '18 at 23:01
  • 1
    @JW - Yes that'd be considered secure if the plugin works by securely storing secrets in a hardware backed cryptographic storage. You'd just have to be sure your auth logic isn't shared with any web version of the app since you won't have secure storage on a browser. Alternatively, there are AppAuth implementations for mobile that you might be able to load prior to a Cordova app? I'd think it more secure if you left all interactions with tokens and storage native to the platform and build a bridge for Cordova. If security is paramount, better off going native (or similar) vs Cordova IMHO. – someone1 Dec 31 '18 at 19:54
  • The latest recommendation draft now says you "must use PKCE" and lots of other mandating language around PKCE usage in SPAs https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps#section-7 – java-addict301 May 18 '22 at 13:03

3 Answers3

25

@catanman makes excellent points regarding the technical considerations around PKCE in SPAs, however just recently the IETF Oauth working group has published a best current practice document (December 28, 2018) stating:

Note: although PKCE so far was recommended as a mechanism to protect native apps, this advice applies to all kinds of OAuth clients, including web applications.

Brad J
  • 351
  • 3
  • 3
  • Note that as per RFC 6749 an SPA is not a web application. Web applications are confidential applications running on servers. SPA's are classified as user-agent-based application. – Priya R Feb 15 '19 at 10:18
  • 8
    Though still incorporated with "all kinds." – Brad J Feb 16 '19 at 16:26
  • Great find - it'd be interesting to see how this draft develops! – someone1 Mar 04 '19 at 04:00
  • 1
    Excellent video June 2019: https://www.youtube.com/watch?time_continue=615&v=CHzERullHe8. Note specifically Aaron Perecki's quote: "It [PKCE] was created for mobile apps, but it turns out it is useful for any public client." – ryanm Jul 03 '19 at 21:13
25

While all the other answers are correct, the latest OAuth 2.0 for Browser-Based Apps Best Practices Doc (January 29, 2019) states that (emphasis mine):

  1. Overview

For authorizing users within a browser-based application, the best
current practice is to

o Use the OAuth 2.0 authorization code flow with the PKCE extension

...

As well as

7.1. Initiating the Authorization Request from a Browser-Based Application

Public browser-based apps MUST implement the Proof Key for Code
Exchange (PKCE [RFC7636])
extension to OAuth, and authorization
servers MUST support PKCE for such clients.

The PKCE extension prevents an attack where the authorization code is intercepted and exchanged for an access token by a malicious client, by providing the authorization server with a way to verify the same client instance that exchanges the authorization code is the same one that initiated the flow.

I assume there are some minor benefits that are worth the while of doing it as a defense in depth, even for browser based apps.

Eran Medan
  • 811
  • 1
  • 10
  • 19
  • 2
    Great find - it'd be interesting to see how this draft develops! – someone1 Mar 04 '19 at 03:57
  • 4
    Section 9.8 ( https://tools.ietf.org/html/draft-ietf-oauth-browser-based-apps-01#section-9.8 ) have some good arguments for why this is better than implicit flow. Best argument IMO: "_Additionally, many browsers now also sync browser history to cloud services and to multiple devices, providing an even wider attack surface to extract access tokens out of the URL._" – Stian Fauskanger Apr 23 '19 at 13:52
  • 1
    I understand why Authorization Code is preferred over Implicit. However, **for a browser-based app**, I don't see why PKCE is any more secure than the normal Authorization Code flow with a randomly-generated `state` param. The PKCE `verifier` mitigates attacks where the `code` has to be passed back to the application over an insecure channel (as in mobile apps). But modern-day browser apps use can use CORS and TLS to talk directly to the authentication server, meaning the `code` should be securely returned to the app. – mamacdon Jul 29 '20 at 14:31
17

SPAs would not benefit from PKCE. PKCE solves a different problem than the one you're describing.

First of all, for SPAs the current best practice is still to use the implicit flow, not the authorization code flow. With the implicit flow, the access token is included in the hash fragment (#) of the redirect URI instead of in a query component (?). Since the browser never includes the hash fragment portion of a URI when it makes a request, the token does not appear in browser history, web logs...

When it comes to native apps, rfc8252 section 6 says the following:

Public native app clients MUST implement the Proof Key for Code Exchange (PKCE RFC7636])


On a side note, notice that the PKCE requirement is for public native clients. You may be wondering how it can be possible for a native app to not be public. The answer is in section 8.4:

Except when using a mechanism like Dynamic Client Registration [RFC7591] to provision per-instance secrets, native apps are classified as public clients

I'm not exactly sure how to refer to these clients since I've never seen confidential native client mentioned anywhere. Maybe non-public native client works :)


To answer your question: So why is the authorization code flow with PKCE required for public native apps and not SPAs?

The logic is as follows:

  1. One of the main purposes of OAuth2 is to prevent the exposure of user credentials to clients.
  2. For native apps to meet this requirement, the user must not enter credentials anywhere that the native app has access. Therefore native login screens and webviews must be avoided.
  3. The solution is for native apps to launch an external user-agent (see rfc8252 appendix B) where the user will authenticate with the authorization server and authorize the application. Native apps have no access to the external user-agent, so the user's credentials are safe.
  4. However, a new complication has been introduced which did not exist with SPAs: The external user-agent must now communicate the authorization server's response back to the native app? The answer is inter-app communication (see section 5 and section 7)
  5. Unfortunately, several types of inter-app communication can be intercepted by malicious 3rd party apps! With the implicit flow, this means that the access token could be stolen by a malicious app, which would be a very bad thing. This is one of the main reasons why the implicit flow is not used for native apps.
  6. But even if the authorization code flow is used instead, the malicious 3rd party app can still intercept the authorization code, and use it to obtain an access token since there is no client secret. So it seems useless to use the authorization code flow instead of implicit flow for public native apps.
  7. This is where PKCE comes in. PKCE makes it so that even if a malicious app intercepts an authorization code, it will not be able to exchange it for an access token. This is accomplished by requiring the entity that is requesting the access token prove that it is the same entity that requested the authorization code in the first place.

Hopefully now you understand why:

  • SPAs would not benefit at all from PKCE since with SPAs everything occurs within the browser. PKCE is only useful when there is inter-app communication.
  • Public native apps should not use the implicit flow because inter-app communication can sometimes be insecure.
  • Public native apps should use the authorization code flow + PKCE

Here is a good blog post that can provide more info: https://medium.com/@justinsecurity/mobile-apps-and-oauths-implicit-flow-68e72c6515a1

el_tigro
  • 694
  • 8
  • 14
  • 3
    Thanks for the write up! I think I understand most of that but a few points here: There has been talk about ditching the implicit flow for SPAs (see [here](https://aaronparecki.com/oauth-2-simplified/#single-page-apps)). Although PKCE solves a different problem, my argument is that it's better than just plain implicit flows, more of a defense-in-depth mechanism. Hash fragments DO end up in the browser history if you alter them, which is arguably a common use-case in a SPA - though your point about weblogs is valid! – someone1 Jun 11 '18 at 19:58
  • Also forgot to mention that some providers, such as Google, will not allow the use of `prompt=none` for implicit flows - though I don't think this is to spec. – someone1 Jun 11 '18 at 20:13
  • I’m not sure I follow the argument here. @catanman says “With the implicit flow, the access token is included in the hash fragment (#) of the redirect URI instead of in a query component (?).” except it doesn’t matter with the code flow, because your access_token doesn’t come back via the query in that flow either. – Steve Jul 15 '18 at 23:16
  • Also, the term for non-public clients is “confidential clients” and these refer to server-side apps. – Steve Jul 15 '18 at 23:18
  • 2
    "First of all, for SPAs the current best practice is still to use the implicit flow, not the authorization code flow." - [this article](https://www.oauth.com/oauth2-servers/single-page-apps/) appears to suggest otherwise. You have a good point on the relative un-importance of PKCE for an SPA, but I don't think your assertion regarding implicit vs. auth code flows for SPAs is valid. – Jake Feasel Jul 27 '18 at 18:23
  • 1
    Steve: The OP suggests that the Implicit flow is less secure than the Authorization Code flow (as mentioned in the linked article) because with the Implicit flow, access tokens will end up in web logs and the browser history. I thought that this was incorrect since the Implicit flow uses hash fragments. As the OP mentions in the first comment: hash fragments DO end up in the browser history. – el_tigro Jul 27 '18 at 20:42
  • 2
    In the latest OAuth 2.0 recommendation for browser based apps (internet draft) it seems they added browser based apps as required to use PKCE. See my answer below for more details. – Eran Medan Mar 07 '19 at 04:31
  • `Since there is no client secret.` Why, we can always have client secret that way do we still have need for PKCE? – Suraj Jain Feb 23 '20 at 12:46
  • 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. Is this statement from Auth0 right? https://auth0.com/blog/oauth2-implicit-grant-and-spa/ draft spec: https://tools.ietf.org/html/draft-ietf-oauth-security-topics-11 – Braulio Jul 13 '20 at 11:24
  • 2
    @SurajJain The browser is not a secure environment for storing a client secret, and thus, client secrets can not be used in the implicit/auth-code+pcke flows. – jmrah Jul 20 '20 at 01:44
  • What I still don't get is: You're adding the code_challenge to the auth request. fine. that can't be altered with, due to encryption. fine. But when doing the actual call to the token endpoint, if I understand correctly, the verifier is sent unmodified. So: What prevents an attacker, be it browser or mobile, to also intercept that request when code_challenge is present in the first one, and call the token endpoint with code and verifier? – Dominik Oct 21 '21 at 09:33
  • @Dominik [TLS](https://security.stackexchange.com/a/206677/148211) – Leponzo Feb 10 '22 at 08:58
  • @Leponzo no, at least not for the mobile part where the whole pkce addition is originated in. PKCE is exactly made for use cases, where you can't rely on TLS. see https://datatracker.ietf.org/doc/html/rfc7636#section-1 – Dominik Feb 10 '22 at 16:31
  • @Dominik "In this attack, the attacker intercepts the authorization code returned from the authorization endpoint **within a communication path not protected by Transport Layer Security (TLS), such as inter-application communication within the client's operating system.**". Requests to the Token and Authorization endpoint must always be done over TLS. The part that's not TLS protected is the inter-app communication (via custom URI scheme) which is handled internally by the OS. Only the `code` is transmitted through this path. – el_tigro Feb 10 '22 at 18:12
  • I think there are a lot of browser extensions that can "intercept" the visited URLs. For that reason SPAs could also benefit from PKCE. – Gandalf Jul 05 '22 at 21:29