When a native application needs access to a user’s account on a given online service, there are two main alternatives: 1) rely on accounts previously configured on the device; 2) interact directly with the online service to get the needed authorization. The former approach builds directly on features exposed by the OS and usually requires the app to have specific permissions. This is the case of Android’s AccountManager. The latter usually relies on some authorization protocol supported by the target service, which nowadays is likely to be OAuth 2.0. In this case the application will require some sort of user-agent to perform the authorization flow.
Google’s Identity team recently open sourced AppAuth for Android and iOS, a client library that enables native applications to perform OAuth 2.0 authorization flows in a secure and usable manner. AppAuth follows the recent IETF draft of best practices for authorization flows on native applications.
The OAuth 2.0 RFC already includes a section around native applications, reminding that these clients can’t keep their credentials confidential and providing two main approaches to perform the authorization flow: embedded user-agents and external user-agents (e.g. system default browser).
Embedded user-agents are usually a native component that the application directly communicates with. This is the case of WebViews in Android. Embedded user-agents are a very common option because they don’t require the user to switch context and there wasn’t a better approach to overcome this aspect (not anymore, as we’ll see later on). However, this approach has many usability and security drawbacks. Regarding usability, embedded user agents don’t share the authentication state with other apps or the system browser, meaning the user might have to re-authenticate. On the other hand, users are authenticating on an unidentified window without the usual browser features, which makes it harder for them to know if they are singing in to the legitimate site and educates them in the wrong way. Worst than that, the host application has access to the whole user-agent context, such as cookies and key strokes. Finally, since embedded user-agents are native components they might not be so easily updated as an external user-agent, which is likely to be just another application on the OS.
When using external user-agents, in order to perform an OAuth authorization request, an application needs to somehow activate the external browser. Most environments provide URI-based inter-app communication (e.g. Intent on Android) which fit OAuth 2.0, since authorization requests are HTTP URIs. In this case, the authorization flow is executed in the browser, which is registered on the OS as knowing how to handle HTTP URIs. The authentication state is shared, avoiding re-authentications, and the user faces a familiar experience and may rely on browser features for authentication (stored credentials, extensions, etc). In order to get the authorization response, the most common approach is to use custom schemes on OAuth redirect URIs. All the major platforms have support for binding custom URI schemes to applications. When the browser (on any other application) attempts to follow such an URI, the corresponding application is activated. This is how the last redirect on the authorization flow is delivered to the initiator.
The IETF draft of best practices for authorization flows on native applications adds some restrictions and recommendations for this scenario:
- Authorization flows should be delegated to external user-agents via URI-based communication. Embedded user-agents are not recommended.
- Whenever possible in-app browser tabs should be used. In-app browser tabs are provided by a browser in the system but displayed within the application with limited navigation capabilities. However, they cannot be directly accessed by the application and they share the browser’s authentication state and security features. On iOS this feature is available via SFSafariViewController and iOS 9+. On Android, the feature is available via Chrome Custom Tabs since Chrome 45 (Sep 2015) from Jellybean on. Both versions support some customization, such as changing the navigation bar color, adding custom share buttons and going back to the application in one tap..Note that this feature can be supported by other browsers installed on the devices. Also note that this feature is not restricted to authorization flows. In-app tabs are already used by some applications, such as Facebook and Twitter, to display generic external web content.
- Applications should use custom URI schemes that are globally unique for their redirect URIs. The recommend strategy is using the reverse domain name pattern applied to a domain under the application publisher control.
- Applications must use Proof Key for Code Exchange by OAuth Public Clients (PKCE) when using custom URI schemes. Since multiple applications might register the same custom scheme, the OS could activate a malicious application to receive the authorization response. PKCE “is a Proof of Possession extension to OAuth 2.0 that protects the code grant from being used if it is intercepted“. Read this post for more details on PKCE and the underlying vulnerability (applies to public clients, which is the case of most native applications).
The recently open sourced AppAuth library for Android and iOS was implemented with all these considerations in mind, so I think it’s worth mentioning. In addition, it supports custom extensions on OAuth requests and includes fallback to the default browser if a browser supporting custom tabs is not found. Seems to do all the plumbing to support a uniform and secure authorization experience.