Best Practices - OAuth for Mobile Apps
On this page
Mobile apps pose a different kind of problem than other client types when using OAuth. Being distributed, using app stores offers a lot of advantages, but for developers trying to get tokens to access their APIs, some problems will surface.
Mobile apps are by default considered public clients. They can't be given a credential to authenticate with, since that credential would have to be delivered through the app store and could then easily obtained by a malicious user. Being public clients, they can be allowed to run the OAuth code flow, without authenticating to the token endpoint. This creates some security issues that we're going to look at how to solve.
In a mobile app, it's recommended to perform the authorization request inside a system browser component. This enables several benefits.
Single Sign On
Because using the system browser component of your platform will allow the use of the shared cookie jar, getting Single Sign On (SSO) between the system browser, the app, and other apps on the same device using the same OAuth provider.
Separating authentication methods from the apps
Since the authentication happens in the browser and on the server, the method used can be updated independently on the server side without creating app releases.
Increased user privacy
The user will only give the credentials directly to their trusted provider, and the app will not be able to read the user credentials.
- Android: Custom Tabs
- iOS 13+:
- iOS before 13:
- Others: the default browser
In mobile operating systems it's possible for an app to register that it can handle a custom protocol handler. This custom protocol can then be used in the
redirect_uri of the OAuth client to get back to the app after the authentication happened in the browser. For instance, if we have an app, the
CurityExample, it could register that it can handle the protocol
io.curity.client://. This means that we can register the uri
io.curity.client://callback as the
redirect_uri for the client. As we know the code flow, this is were the code will be delivered as a query parameter in the redirect, i.e.
Since the registration of this protocol handling is done by the app itself, there is always the possibiliy that another app can register the same uri and receive the code. Since the client is a public client, all the malicious app has to do now is to send this
code to the token endpoint, in order to receive the tokens.
There are two ways to mitigate this. First we have the Universal Links or App Links. In a similar way, the app can register an external URL that should trigger the app. When the OAuth server redirects to this host, the platform will trigger the app in the same way as before. The difference is that there is a document hosted on the host (different path/format depending on mobile platform) that describes what application to trigger. In this way, the server can be authenticated if it's using TLS, and also no malicious app should be triggered.
The other way is using Proof Key for Code Exchange (PKCE). Since PKCE will prevent the malicious app from using the received code, it's effectively blocks the above attack. It can also be combined with the Applink to not trigger the malicious app in the first place.
Using public clients makes it harder for the OAuth Server to trust the client, since the client never authenticates itself. The only thing it presents is a
client_id, which could quite easily be found. To increase the level of trust, mobile apps are recommended to use Dynamic Client Registration to generate a unique app instance. If the app register as a client upon installation, it will receveie a unique set of credentials to be used to authenticate against the token endpoint. This way, the OAuth Server will know that each token request is for the same instance. But there's still one problem, since the registration is done using something delivered using an app store and can be easily extracted by an attacker. The situation is a little bit better since we have unique instances, which would make it harder to make a larger scale attack. But it could still be a non legitimate app on the other side.
The registration endpoint can be protected with a token scoped to
dcr, and to get a hold of the token you can either use client credentials with compiled in credentials, or using implicit flow. Using implicit flow, the user get s to authenticate, and has a chance to see if the app that's trying to register is a legitimate app.
When the user has authenticated, and the app is registered, we have a link between the user and the unique instance of the app. This means that we can prevent tokens to this client to be issued to other users, and if the app misbehaves in some way, we can report to the user that it installed a malicious app. You could potentially also list all the apps a user has registered into the user portal and allow the user to revoke individual apps if needed.
- Use the system browser component for increased user privacy
- Use PKCE to mitigate against code interception
- Register using Implicit flow to create unique app instances tied to a user