Curity OAuth for mobile apps best practices

Best Practices - OAuth for Mobile Apps

On this page

Implementing OAuth for mobile apps presents unique challenges due to the security threats on many distributed devices. While app stores enable secure mobile app installation, there are environment-specific threats, like a malicious app that impersonates the real app and receives access tokens with which to call APIs.

Mobile apps are by default considered public clients that cannot use a client credential to authenticate at the authorization server's token endpoint. Any fixed secret would have to be published with the app. A malicious user might be able to decompile the app or intercept HTTP requests to get the client credential and use it in another app.

This article explains some techniques you can use to harden mobile security. This starts with securely authenticating users and preventing impersonation. The app then needs to work with backend components to protect its tokens.

Use the System Browser for Secure Authentication

The RFC 8252 - OAuth for Native Apps document explains how a mobile app should run user logins using a system browser component. The app forms an authorization request URL and opens the system browser at that location. This approach has several benefits.

1. Protects User Privacy

The user only gives credentials to a specialist identity system, and the mobile app never has access to user credentials. This minimizes the attack surface and the user is encouraged to only disclose user credentials at a secure location when using your organization's applications.

2. Externalizes Authentication Complexity

Since the browser and the authorization server manage user authentication, the app only implements a Code Flow. Once integrated, you can change the authentication flow without needing to update application code and deploy new releases to app stores.

3. Enables Single Sign-On

Using the system browser component of your platform allows the use of the shared cookie jar, enabling Single Sign-On (SSO) between the system browser, the app, and other apps on the same device that use the same authorization server.

Recommended Browser Components for Mobile Platforms

Mobile platforms provide integrated system browsers that overlay the app's views and provide a user experience similar to that of a modal dialog. As a result, logins never leave the app and the user sees the authorization server's domain in the browser's address bar.

Use Custom Tabs on Android or ASWebAuthenticationSession on IOS. For older iOS devices before iOS 13, use SFSafariViewController. If you need to support minority mobile platforms that do not have an integrated system browser, the app can run a code flow on the main system browser on those devices.

Using the system browser and an authorization server should not limit how user authentication works, since the authorization server can provide extensibility features to enable customization. For example, authentication may be able to integrate with existing data sources or the authorization server can provide an SDK that enables custom authentication methods.

Use a Secure Redirect URI

A mobile app can register a custom protocol handler with the operating system. In OAuth, this is also known as a private-use URI scheme. To uniquely identify an organization and its application, use a domain-based naming convention like com.example.app or io.curity.client. The OAuth client registration for the mobile app then uses a redirect URI such as io.curity.client:/callback.

After a user authenticates, the authorization server delivers the authorization response to a browser URL such as io.curity.client:/callback?code=<random_string>. The browser passes this request to the operating system, which in turn invokes the mobile app, so that it receives the authorization response. The mobile app can then redeem the code for a set of tokens and call APIs with its access token.

The system browser may require a user gesture during the code flow before it invokes the app with the authorization response. One way to receive authorization responses reliably is for the mobile app to include the OpenID Connect prompt=login parameter when it sends the authorization request.

Use Universal Links or App Links to Prevent Impersonation

Mobile platforms do not restrict custom protocol handlers to a single application. Therefore, an attacker could potentially install an app that registers the same custom protocol handler as the genuine mobile app. That app could form an authorization request using the genuine app's client_id and redirect_uri. If the app can trick a user into authenticating, or use an existing SSO session, it may be able to receive an authorization response. Since the client is a public client, the malicious app only has to send the code to the token endpoint to get tokens and gain API access.

You can prevent this type of impersonation attack using HTTPS redirect URIs. The main mobile platforms have built-in support for HTTPS notifications, like Android App Links and iOS Universal Links. To use these features you must register an assets document at a trusted HTTPS endpoint that you own, which associates the app's identity and code signing details to the HTTPS domain. Some example assets document locations are shown below.

  • Android Assets Document: https://mobile.example.com/.well-known/assetlinks.json.
  • iOS Assets Document: https://mobile.example.com/.well-known/apple-app-site-association.

During mobile app installation, the operating system verifies the app's identity and code signing details against the online document. If verification succeds, the app can be invoked using HTTPS URLs. Otherwise, as would be the case for a malicious app, the registration fails. This means only the genuine app can receive an authorization response.

However, the browser may fail to send an HTTPS link to the operating system when the browser request is a redirect response. To overcome this type of issue you can use an external redirect URI, where you host a web page at a location such as https://www.example.com/mobileapp/callback. The web page could prompt the user for a gesture, such as a button click, then run JavaScript logic that forwards the authorization request parameters to the app. The following example code snippet demonstrates the approach.

javascript
123456789
document.getElementById('continueButton').onclick = () => {
var appReturnUri = 'https://mobile.example.com/callback';
if (window.location.search) {
appReturnUri += window.location.search;
}
window.location.href = appReturnUri;
};

Use PKCE to Protect Authorization Responses

Mobile apps should always use Proof Key for Code Exchange (PKCE). That article explains how PKCE protects against code interception and code injection threats. As a result, an attacker cannot get tokens solely by stealing an authorization code from a genuine response and sending it to the authorization server's token endpoint.

Use Confidential Access Tokens

Once user authentication completes, the mobile app has access tokens and can call APIs. However, mobile clients should not use JWT access tokens since they are easily readable by attackers and disclose sensitive claims that only APIs should be able to read. The authorization server should enable you to issue access tokens and refresh tokens to mobile clients in a confidential and unreadable format.

The most efficient solution is opaque tokens, which are unguessable values similar to UUIDs. The mobile client can send its opaque access token in API requests and an API gateway can use OAuth introspection to get a JWT access token to forward to the API. Such an approach preserves privacy, where only backend components receive JWTs with sensitive claims. Read more about this approach in the Phantom Token Approach article.

Protect Tokens

The mobile app, and mobile developers who build them, must protect tokens and avoid insecure practices such as logging them or sending them in URLs. In high security use cases, such as a banking app, store tokens only in memory.

A medium security app might persist tokens in secure storage private to the app, to prevent users needing to reauthenticate on every app restart. For example, an Android app could store tokens in shared preferences or an iOS app could store tokens in the iOS keychain. When doing so, think carefully about threats such as stolen devices. For example, require the user to have a secure lock screen configured before persisting tokens to the device.

Run with Least Privilege

Mobile apps usually always use bearer access tokens, so limit the impact if one is stolen. Use Scopes so that access tokens can only be used at particular APIs. Also keep access tokens short-lived, with a lifetime such as 15 minutes. If an attacker somehow intercepts an access token, an exploit only lasts for a short time.

Harden Token Requests

Since mobile devices are public clients, an attacker who intercepts a refresh token can use it to get new access tokens and mount a longer-lived exploit against APIs. To prevent this, use one of the following techniques to strengthen all token requests. In particular, these approaches mitigate refresh token exploits.

1. Mobile Device Management

For use cases where you control all devices, you may be able to deploy a distinct client certificate and key to each device and grant the app access to the key material. If the app can send the client certificate during requests to the token endpoint, you can build a hardened OAuth flow using Mutual TLS Sender Constrained Access Tokens. This enables proof-of-possession access tokens, so that an attacker cannot exploit intercepted tokens and gain API access.

2. Application Attestation

Modern mobile devices use a hardware-backed key issued by the device provider, like Google or Apple. An application can request that the operating system sign a challenge using that key, to assert the mobile app's identity. Each app can only get a signature for its own package or bundle ID. The app can then send the signature to a server that verifies it.

A mobile app and server component can use software attestation frameworks like Google Play Integrity or Apple App Attest that perform the lower level verification and produce an attestation JWT that the mobile app can send to server components. An extension to the authorization server's logic could potentially verify such a JWT during requests to the token endpoint.

In effect, an attestation JWT can serve as a client credential during token requests and also prevent impersonation. A malicious app cannot exchange an authorization code for tokens if it must also also supply a valid attestation JWT. If an app uses attestation to protect token requests it no longer needs to protect authorization responses using HTTPS redirect URIs.

3. Dynamic Client Registration

If there is no support for attestation, mobile apps can use Dynamic Client Registration (DCR) so that the mobile app uses a distinct client_id and client secret on each device. The authorization server then has an increased level of trust that the right client is present. It is also more difficult for an attacker to mount a large scale attack, since the attacker does not know the client IDs and client secrets of remote instances.

To secure the registration endpoint, require an initial access with a scope of dcr. The mobile app can use a DCR Authentication Method to get the initial access token in various ways.

DCR can establish a link between the user and their dynamic client(s). If there is malicious use of a particular dynamic client's access token, you could inform the user that their device may contain a malicious app. You could also potentially list all of a user's dynamic clients in a portal application and allow the user to revoke access for particular clients.

DCR adds some overhead to the authorization server, since any updates to client settings must be rolled out to all dynamic clients. For that, use the Dynamic Client Registration Management standard. The Curity Identity Server provides a solution called Templatized DCR that minimizes duplication of client settings.

Consider Native Authentication for First-Party Apps

Although you should understand the system browser's security benefits, it does not always add security value. For example, when using some modern authentication methods, like Passkeys, the app never comes into contact with user credentials so it is more natural to use a native passkeys login experience. In other cases, the app may need to capture proofs of the user's identity using device capabilities that the browser cannot access.

The OAuth 2.0 for First-Party Native Applications draft specification discusses this concept, for use cases where the same organization provides the authorization server and the mobile app, and flows do not use any third-party user credentials. If the authorization server supports it, the mobile app can use an API-driven authentication flow that bypasses the system browser. Such a flow could include additional requests to those in OAuth specifications, to perform client attestation before allowing an authentication flow to begin.

The Curity Identity Server supports this approach. You can use the Hypermedia Authentication API (HAAPI) to integrate OAuth in a low-code manner, with a pure native experience, and ensure a Hardened Mobile Security Lifecycle.

Summary - OAuth for Mobile Apps Best Practices

Curity recommends the following best practices when integrating OAuth into mobile apps. To run some example apps that use the best practices, see our Mobile Code Examples.

  • Use the system browser component to ensure the privacy of user credentials.
  • Use a secure redirect URI and prevent an attacker from impersonating the app.
  • Use PKCE to mitigate against code interception and code injection attacks.
  • Use confidential access tokens so that mobile apps cannot read data intended for APIs.
  • Protect tokens carefully in mobile apps, to prevent an attacker getting bearer tokens.
  • Issue least-privilege access tokens to mobile apps to reduce the impact of any security exploits.
  • Issue short-lived access tokens to mobile apps to reduce the time of any security exploits.
  • Consider using attestation to prevent an attacker from exploiting an intercepted refresh token.
  • Consider native authentication for first-party apps when the system browser does not add security value.
Photo of Gary Archer

Gary Archer

Product Marketing Engineer at Curity

Newsletter

Join our Newsletter

Get the latest on identity management, API Security and authentication straight to your inbox.

Newsletter

Start Free Trial

Try the Curity Identity Server for Free. Get up and running in 10 minutes.

Start Free Trial

Was this helpful?