Introduction

RFC 8252 - OAuth 2.0 for Native Apps, published on October 2017 as a Best Current Practice, contains much needed guidance on how to use the OAuth 2.0 framework in native applications. In this document, published originally as a post on https://blog.pedrofelix.org/2017/10/24/rfc-8252-and-oauth-2-0-for-native-apps/, we present a brief summary of the best practices defined on that RFC.

OAuth for native applications

When the OAuth 1.0 protocol was introduced, its main goal was delegated authorization for client applications accessing services on behalf of users. It was focused on the model “du jour”, where client applications were mostly server-side rendered Web sites, interacting with end users via browsers. For instance, it was assumed that client applications were capable of holding long-term secrets, which is rather easy for servers but not for browser-side applications or native applications running on the user’s device.

OAuth 2.0, published on 2012, introduced a framework with multiple different flows, including the support for public clients, that is, clients that don’t need to hold secrets. However it was still pretty much focused on classical Web sites and using this framework in the context of native applications was mostly left as an exercise for the reader. Some of the questions that didn’t had a clear or straightforward answer were:

  • What is the adequate flow for a native application?
  • Should a native application be considered a confidential client or a public client?
  • Assuming an authorization code flow or intrinsic flow, how should the authorization request be performed: via an embedded web view or via the system browser?
  • How is the authorization response redirected back into the client application, since it isn’t a server any more? Via listening on a loopback port or using platform specific mechanisms (e.g. Android intents and custom URI schemes)?
  • What’s the proper way for avoiding code or token leakage into malicious applications also installed in the user’s device?

The first major guidance to these questions came with RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients, published in 2015. This document defines a way to use the authorization code flow with public clients, i.e. adequate to native applications, protected against the interception of the authorization code by another application (e.g. malicious applications installed in the same user device). The problem that it addresses as well as the proposed solutions are described on a previous post: OAuth 2.0 and PKCE.

The recently published RFC 8252 - OAuth 2.0 for Native Apps (October 2017) builds upon RFC 7636 and defines a set of best practices for when using OAuth 2.0 on native applications, with emphasis on the user-agent integration aspects.

In summary, it defines the following best practices:

  • A native client application must be a public client, except if using dynamic client registration (RFC7591) to provision per device unique clients, where each application installation has an set of secret credentials) - section 8.4.

  • The client application should use the authorization code grant flow with PKCE (RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients), instead of the implicit flow, namely because the later does not support the protection provided by PKCE - section 8.2.

  • The application should use an external user-agent, such as the system browser, instead of an embedded user-agent such as a web view - section 4.

    • An application using a web view can control everything that happens inside it, namely access the user’s credentials when they are inserted on it. Using an external user-agent isolates the user credentials from the client application, which is one of the OAuth 2.0 original goals.

    • Using the system-browser can also provide a kind of Single Sign-On - users delegating access to multiple applications using the same authorization server (or delegated identity provider) only have to authenticate once because the session artifacts (e.g. cookies) will still be available.

    • To avoid switching out of the application into the external user-agent, which may not provide a good user experience, some platforms support “in-app browser tabs” where the user agent seems to be embedded into the application, while supporting full data isolation - iOS SFAuthenticationSession or Android’s Chrome Custom Tabs.

  • The authorization request should use one of the chosen user-agent mechanism, by providing it with the URI for the authorization endpoint with the embedded request on it.

  • The redirect back to the application can use one of multiple techniques.

    • Use a redirect endpoint (e.g. com.example.myapp:/oauth2/redirect) with a private scheme (e.g. com.example.myapp) that points to the application. Android’s implicit intents are an example of a mechanism allowing this. When using this technique, the custom URI scheme must be the reversal of a domain name under the application’s control (e.g. com.example.myapp if the myapp.example.com name is controlled by the application’s author) - section 7.1.

    • Another option is to use a claimed HTTPS redirect URI, which is a feature provided by some platforms (e.g. Android’s App Links) where a request to a claimed URI triggers a call into the application instead of a regular HTTP request. This is considered to be the preferred method - section 7.2.

    • As a final option, the redirect can be performed by having the application listening on the loopback interface (127.0.0.1 or ::1).

To illustrate these best practices, the following diagram represents an end-to-end OAuth 2.0 flow on a native application

OAuth 2.0 Native Flow

  • On step 1, the application invokes the external user-agent, using a platform specific mechanism, and passing in the authorization URI with the embedded authorization request.

  • As a consequence, on step 2, the external user-agent is activated and does a HTTP request to the authorization endpoint using the provided URI.

  • The response to step 2 depends on the concrete authorization endpoint and is not defined by the OAuth specifications. A common pattern is for the response to be a redirect to a login page, followed by a consent page.

  • On step 3, after ending the direct user interaction, the authorization endpoint produces a HTTP response with the authorization response embedded inside (e.g. 302 status code with the authorization response URI in the Location header).

  • On step 4, the user-agent reacts to this response by processing the redirect URI. If using a private scheme or a claimed redirect URI, the user-agent uses a platform specific inter process communication mechanism to deliver the authorization response to the application (e.g. Android’s intents). If using localhost as the redirect URI host, the user-agent does a regular HTTP requests to the loopback interface, which is being listened by the application, thereby providing the authorization response to it.

  • On steps 5 and 6, the application exchanges the authorization code for the access and refresh tokens, using a straightforward token request. This interaction is done directly between the client and the token endpoint, without going through the user-agent, since no user interaction is needed (back-channel vs. front-channel). Since the client is public, this interaction is not authenticated (i.e. does not include any client application credentials). Due to this anonymous characteristic and to protect against code hijack, the code_verifier` parameter from the PKCE extension must be added to the token request.

  • Finally, on step 7, the application can use the resource server on the user’s behalf, by adding the access token to the Authorization header.

The AppAuth libraries for iOS and Android already follows these best practices.