Enhancing OAuth Security for Mobile Applications with PKCE

OAuth 2.0 is the preferred mechanism for authorizing native mobile applications to their corresponding API endpoints. In order to be authorized, the native application attaches an OAuth access token to its API calls. Upon receiving a call, the API extracts the token, validates it (checks issuer, lifetime, associated authorizations, etc) and then determines whether the request should be allowed or denied.

Of course, before the native application can use an access token on an API call, it must necessarily have first been issued that token. OAuth defines how the native application, with a user’s active involvement, interacts with an Authorization Server (AS) in order to obtain a set of tokens that represent that user and their permissions. The best practice for native applications leverages a version of OAuth called the ‘authorization code grant type’ – which in this context consists of the following steps

  1. Upon installation, the native application registers itself with the mobile OS as the handler for URLs in a particular scheme, e.g. those starting with ‘com.example.mobileapp://’ as opposed to ‘http://’.
  2. After installation, the native application invites the user to authenticate.
  3. The native application launches the device system browser and loads a page at the appropriate AS.
  4. In that browser window, the AS
    • authenticates the user. Because authentication happens in a browser, the AS has flexibility in the how & where the actual user authentication occurs, i.e., it could be through federated SSO or could leverage 2 Factor Authentication etc. There are advantages to using the system browser and not an embedded browser – notably that a) any credentials presented in the browser window are not visible by the application b) any session established in the browser for one native application can be used for a second, enabling a SSO experience
    • may obtain the user’s consent for the operations for which the native application is requesting permission
  5. If step 4 is successful, the AS builds a URL in the scheme belonging to the native application and adds an authorization code to the end of the URL, e.g. ‘com.example.mobileapp://oauth?code=123456. The AS directs the user’s browser to redirect to this URL
  6. The browser queries the mobile OS to determine how to handle this URL. The OS determines the appropriate handler, and passes the URL to the appropriate application
  7. The native application parses the URL and extracts the authorization code from the end
  8. The native application sends the authorization code back to the AS
  9. The AS validates the authorization code and returns to the native application an access token (plus potentially other tokens)
  10. The native application then stores that access token away in secure storage so it can be subsequently used on API calls.

The current reality is that there is a security risk associated with Steps 6-8 above that could result in a malicious application being able to insert itself into the above flow and obtain the access token – and so be able to inappropriately access the business or personal data stored behind the API. The risk arises due to a combination of factors

  1. The nature of how native applications are distributed through public stores prevents individual instances of applications having unique (or secret) credentials. Consequently, it is not currently practical to expect that the native application can authenticate to the AS when exchanging the code for tokens in Step 8. As a result, if a malicious application is able to get hold of the code, it will be able to exchange that code for the desired tokens.
  2. In Step 6, the handoff of the authorization code can be intercepted if a malicious application is able to ‘squat’ on the URL scheme, i.e., get itself registered as the handler for those URLs. The mobile OSs differ in how they protect against such squatting – for instance, Android prompts the user to choose from between multiple apps claiming the same scheme, iOS does not.
  3. The current industry reality is that access tokens are predominantly ‘bearer’ tokens, i.e., any actor that can gain possession of an access token can use it on API calls with no additional criteria (such as signing some portion of the API call with a key associated with the token).

PKCE (Proof Key for Code Exchange by OAuth Public Clients) is an IETF draft specification designed to mitigate the above risk by preventing a malicious application, having obtained the code by scheme squatting, being able to actually exchange it for the more fundamental access token.

PKCE allows the native application to create an ephemeral one-time secret and use that to authenticate to the AS on Step 8 in the above. A malicious application, even if able to steal the code, will not have this secret and so will be unable to trade the stolen code for the access token.pkce

If using PKCE, the overall flow is identical to the above, but with additional parameters added to certain messages. When the native application first loads the AS page in the browser (Step 3 above), it generates a code_verifier string (and may transform it through some mechanism) and passes that as a parameter on the URL. The AS stores away this string before returning the code back to the native application. When the native application then exchanges the code for the access token (Step 8 above), it will include the code_verifier string on that call. If the code_verifier is missing or doesn’t match that previously recorded, the AS will not return the access token.
Even if a malicious application is able to obtain a code, without the corresponding code_verifier it will be unable to turn that code into an access token, and so unable to access the business or personal data accessed through the APIs.

PKCE promises to provide an important security enhancement for the application of OAuth 2.0 to native applications by mitigating the risk of authorization codes being stolen by malicious applications installed on the device. In fact, the PKCE ‘trick’, that of using transient client secrets in order to authenticate to an AS when the client has no long-term secret, is being used in other applications, e.g. the Native Applications (NAPPS) WG underway in the OpenID Foundation .