Applying RAR in OAuth 2 (and GNAP)

Justin Richer
6 min readFeb 22, 2024

--

The Rich Authorization Request extension to OAuth 2, or RAR, is a way to talk about access in the OAuth space beyond what scopes allow, and it is defined in RFC9396. One of the key motivations behind RAR was admitting, as a community, that a lot of times you need more than a set of scope values to properly describe access to an API.

A Larger Scope

Scopes work pretty well in OAuth, but they’re ultimately just a set of strings. It’s worlds better than the all-or-nothing access that OAuth 1 or API keys give you, and if you have an API, they allow you to easily separate read and write access. But what if you want to be more specific? What if you want to, say, let someone access a specific account for certain features for a certain amount of time? Or read in one place but write to another? And what if you want to compose that set of features differently, such as any account for an unlimited time but only if the user’s online? The ways of describing API access are as varied as the APIs being accessed.

When faced with this problem, the first thing that many people realize is that because scopes are just strings, their own API can impose semantics and syntax on those strings. So people add parameters, or define composition rules, or even define a whole query language embedded into the scope string. But those approaches require a lot of complex processing and lack interoperable structure that would let you easily combine protection of multiple kinds of APIs.

What RAR gives you, instead, is an array of JSON objects to describe your API access. So instead of trying to cram everything into a single string, you can now put together an object that defines exactly what you want.

[
{
"type": "payment_initiation",
"actions": [
"initiate",
"status",
"cancel"
],
"locations": [
"https://example.com/payments"
],
"instructedAmount": {
"currency": "EUR",
"amount": "123.50"
},
"creditorName": "Merchant A",
"creditorAccount": {
"iban": "DE02100100109307118603"
},
"remittanceInformationUnstructured": "Ref Number Merchant"
}
]

This object is specific to the API in question and carries with it all the detail that is needed for successful processing. Each kind of API can define its own type value, which in turn defines what’s allowed to go into the rest of the object. And if you need more than one view of things, like read access to A but write access to B, then you can pass in multiple objects in the same structure.

One important question arises out of this: who needs to know this level of detail?

Who Cares About RAR

In this regard, RAR really is built on top of the concept of a scope. In an OAuth delegation, there are four parties. The client, resource owner, authorization server (AS), and resource server (RS).

The four parties in OAuth delegation, illustration from OAuth 2 In Action

These parties have particular relationships, and each of them might care about a RAR object or a scope in a slightly different way. However, the more important question is about which relationship is in play.

Client -> AS: Requesting Access

When requesting an access token, the client needs to be able to describe to the AS what it wants. RAR allows the client to get VERY specific, if the client knows what details it wants ahead of time. Maybe the client has prompted the resource owner for an account identifier, or has learned through some other protocol where the target system is located, or it’s just been configured to know that it needs to ask for specific objects in order to do specific things. In all of these cases, the client can send RAR objects to the AS just like it would a scope, in the hopes of getting an access token that can do what it asks for.

AS -> Client: Granting Access

When the access token is granted, the AS can tell the client which RAR objects have been applied to the token. While this information is no substitute for an API discovery protocol, this approach can let the client differentiate what an access token is good for in different dimensions. For example, a client can ask for a token for an available signing service, and then be granted a token for use at a specific signing service, indicated through the locations field in the RAR object.

AS -> Resource owner: Gathering Authorization

During the delegation process, the AS often needs to prompt the resource owner to see if they’re OK with what’s being delegated. While this starts as the Client->AS request, RAR gives the AS an opportunity to fine-tune the access by asking the resource owner to be specific, or even filling in values that get put into the resulting RAR object. Maybe the client is asking for account access but the resource owner stipulates that it’s only good for the next five minutes. This does come at a usability cost, since it’s much easier to display a list of scope strings with checkboxes. But experience has shown that this list is not a great security measure anyway, since most users won’t change the checkboxes, and often don’t understand the differentiated access being granted.

AS -> RS: Describing Access

The access token itself represents a certain set of rights that have been granted. These can be described in the metadata of the token, available through either a structured token field or an introspection response. In this way, the RS can learn what an access token is good for, and apply its policies appropriately. Does the token grant access for the HTTP GET command on the resource at /photo/123-fda1d? Is this token even good at this specific RS, or is it meant for somewhere else? The RAR object can be used to describe all of this.

Not Everything Has To Match

Finally, it’s important to note that the all of these different branches need not match each other in a single transaction. In one of the applications where I’ve personally deployed RAR, the client never sees the RAR objects. The client knows to ask for a specific scope, and the AS knows that when it sees that scope, the resulting token needs to apply to a whole set of things represented by the current user’s access within the system. The downstream APIs know nothing about users or accounts, but they do know the resources they protect.

As a consequence, the AS translates the client’s incoming scope request to a set of RAR objects that the APIs understand. The APIs never see or care about the scope, and the client never sees or cares about the RAR. In this way, internal API details stay internal and do not leak unnecessarily into the wider system.

However, a different client in this same ecosystem does have insight into the details of the API structure, and therefore its requests do specify RAR objects that target the APIs. These objects are processed in exactly the same way by the API servers, which gives us a powerful parallelism and profound code reuse in production.

GNAP Native

In GNAP, one of our main goals was to see what an OAuth-style system would look like without the constraints and history of OAuth, and one such constraint includes scopes. Consequently, GNAP’s native access rights is an array of objects that look suspiciously like RAR objects. This design is, of course, intentional, and in many ways RAR is the backport of GNAP’s access rights system to work on top of OAuth 2. While GNAP doesn’t have scopes in the same way, GNAP’s reference-based approach to its API design does allow for the use of a simple string to stand in for the objects in question, allowing a request to have both shortcut and fully specified items in the same request.

"access": [
{
"type": "photo-api",
"actions": [
"read",
"write"
],
"locations": [
"https://server.example.net/",
"https://resource.local/other"
],
"datatypes": [
"metadata",
"images"
],
"geolocation": [
{ lat: -32.364, lng: 153.207 },
{ lat: -35.364, lng: 158.207 }
]
},
{
"type": "financial-transaction",
"actions": [
"withdraw"
],
"identifier": "account-14-32-32-3",
"currency": "USD"
},
"dolphin-metadata",
"some other thing"
]

How Can I Use RAR?

RAR support is starting to show up across different vendors, though it’s not universal yet. One of the companies I work for, Authlete, supports RAR natively. Other products can often have RAR grafted on top, since it takes the form of an extra parameter to be processed by an extension or module.

The real value is that we are starting to see API access defined in terms of RAR objects, replacing the awkward and error-prone string composition practices of the past. RAR may seem complex, but when you look at how APIs are defined and scopes are used, the power of that complexity really starts to show its value.

--

--

Justin Richer
Justin Richer

Written by Justin Richer

Justin Richer is a security architect and freelance consultant living in the Boston area. To get in touch, contact his company: https://bspk.io/