OAuth2.0 for First Party Applications

Special treatment for those you trust most

James Collerton
7 min readDec 3, 2023
Less browser, more native

Audience

This article will examine the OAuth2.0 for First Party Applications draft document (which replaced a similar one specifically for native applications). This RFC defines a new authorization server endpoint which can be used to provide an (in most cases) browserless authorization experience.

We will mainly discuss this in the context of native applications, although it can be applied to any first-party app (except for single page applications).

It will require a solid grounding in OAuth2.0, something you can glean from my list of previous articles here. Particularly essential will be an understanding of the Authorization Code with PKCE flow.

It will also help if you are familiar with the OAuth2.0 for Native Apps RFC. For those needing to catch up my article on the subject is here.

Argument

Native apps: a refresher

First of all, let’s define a ‘native app’. A native app is an application developed for a certain operating system. Generally they are available in an app store (Google Play Store, Apple App Store etc.) and have access to device specific functionality: GPS, accelerometer, push notifications and the device hardware. They are also better for offline work as they needs less connectivity (although HTML5 offers some caching).

The code for the native app lives on the device, which is different to web apps which retrieve code from elsewhere and run it in the browser.

Something also worth mentioning is the notion of ‘hybrid apps’. These are downloaded on the app store and can access device functionality, but are HTML rendered in the browser. They typically run a web app through a container or WebView, a browser that can be contained inside of a mobile app.

Why do we need another RFC?

You may, quite rightly, be wondering why we need another RFC applicable to native applications given we already have one.

From my perspective the main impetus is the use of the browser. This is great in some respects — for example it can leverage existing authenticated sessions. However, a lot of the time it can be difficult pulling a user out of the app for authorization, then sending them back on completion.

The other, related thing about the existing native apps RFC is the use of URI claiming. This can be susceptible to a number of security risks. For example, if you’re not using App Links or Universal Links then anyone can write an app that opens when someone visits your site.

This RFC can be used in the case where we would like to provide a native-first experience. For example, a screen in the application that receives a username and password. It’s worth noting that although it’s aimed at a native app experience, it can force the user to authenticate in a browser if the authorization server deems it necessary.

Why only for first party applications?

We define a first party application as one being developed by the same party as the authorization server.

One of the great things about OAuth2.0 is that it allows us to facilitate party A asking for information on party B from party C. For example, I may independently develop an application that requires some of your information from Google. However, as I developed the application (not Google) it would be third-party software.

First party applications give us a higher degree of trust. We try and stay away from users having to enter their credentials on surfaces we don’t own. An example of this is the deprecation of the ROPC grant.

An overview

A visual summary of OAuth2.0 for First Party Apps

The overall flow is:

  1. The first party client makes a request to the authorization server at a new authorization_challenge endpoint (covered shortly). Essentially this allows you to provide credentials (such as a username and password) to the authorization server to be exchanged for an authorization code.
  2. They may receive an error response, which could take the ‘not enough information’ or ‘invalid’ format. In the former case the client may need to supply more information (say a one time code) and repeat step 1.
  3. On success the authorization server returns an authorization code, which can be used in the same way as the authorization code grant.

Note, we’ve skipped the potential for redirecting to the browser from the example flow.

The new Authorization Challenge endpoint

As covered previously, this RFC introduces the new authorization_challenge_endpoint. This receives POST requests and uses the application/x-www-form-urlencoded format expecting:

  • client_id (required): This is used to identify which client we are requesting authorization for. Alternatively we can use an auth session (covered shortly)
  • scope (required): The standard OAuth scopes.
  • acr_values (required): An ACR (Authentication Context Class Reference) is a way of expressing the level of trust we have in a token’s authentication. For example, a token with a high ACR will have been obtained using more strenuous methods than a lower one (think MFA over just username and password).
  • auth_session (required): This is an opaque Id and way of associating multiple requests from the same client. To explain, let’s take an example when a user needs to provide an initial username and password, then a subsequent one time code. After supplying the former the client will receive a auth_session they must provide with the latter to relate the two.

There are three possible responses from the authorization_challenge_endpoint.

  1. A 200 json success response containing only an authorization code
  2. An error response
  3. A redirect response.

The error response contains a mandatory error field which must be one of:

  • invalid_request: The request itself is malformed.
  • invalid_client: There is an issue with a client (unknown, not authenticated correctly).
  • invalid_grant: The grant or auth_session is invalid (for example, it may be expired or revoked).
  • unauthorized_client: The client is recognised, but not permitted to use the OAuth2 for first-party devices grant.
  • invalid_scope: The requested scopes are invalid.

There are then a number of optional fields that may be returned alongside the error field.

  • error_description: A human-readable description of the problem.
  • error_uri: The URI of a human-readable web page with a description of the problem.
  • auth_session: The ID allowing the authorization server to associate related requests by the same client.

The redirect response can be used when the authorization server judges the risk level too high to be dealt with in the application itself, the application cannot handle the required authentication method, or we need to handle an exceptional flow (e.g. account recovery).

Retrieving Tokens and Accessing Resource Servers

Once the client has received the authorization code it can exchange it for an access token in almost the same manner as the authorization code grant. The only exceptions are:

  1. We no longer need to specify the redirect URI (as we never needed one in the first place, no browser remember).
  2. There is an additional error response allowing the authorization server to prompt for more stringent authorization. This can include an auth session Id. This also applies when the client tries to use any refresh tokens provided by exchanging the authorization code.

The authorization_challenge_endpointalso accepts all parameters for the /authorize endpoint.

For example, a recommended extension to the authorization code grant is PKCE. We can tie the code challenge and code verifier to the authorization code using parameters in the same way.

We use the access token as usual. However, the resource server can employ step-up authentication to indicate that the person needs to re-authorize to a higher level.

How do we know they’re a first-party client?

Trying to deal with authorization natively on a client device leads to a number of tricky pitfalls. As mentioned previously, we normally try and push authentication onto surfaces owned by the authorization server.

Initially, we should limit the usage of the endpoint to an allowlist of client Ids. However, as they will be public clients (we can’t put a secret on a native device) we can’t trust the client’s identity just from the Id.

We should aim to use other methods of validating a client’s identity. One method of doing this is using attestation APIs.

Android recently deprecated the SafetyNet attestation API and replaced it with the Play Integrity API which lets you check on your backend requests are coming from a genuine version of your app on a genuine device.

An example of how we might use an attestation API to verify our first party application

Something similar exists for iOS with DeviceCheck.

On top of this it is helpful to implement a number of extra security considerations. This includes using sender-constraint methods (such as DPoP) and if you have multiple apps (one for desktop, one for mobile etc.), the experience should be as similar as possible between them all.

There are also requirements around handling auth sessions. This includes its lifetime, and DPoP binding.

Example user experiences

Let’s round this out with an example. A user logs in with a code sent to their email address and receives an access and refresh token. On using this token to access sensitive data they’re prompted to step-up their authentication with a one-time password (OTP). We will omit any attestation APIs.

An example end to end flow!

Conclusion

In conclusion we have covered the core ideas fuelling the new OAuth2 for first party applications RFC. This includes the new endpoint it provides, app verification and example flows.

--

--

James Collerton

Senior Software Engineer at Spotify, Ex-Principal Engineer at the BBC