Aaaarrrrgggghhhh! Pirate OAuth extensions – RAR, PAR and JAR explained.

Some time ago I wrote a blog about PAR (Pushed Authorization Request), today we are going to expand on that, by discussing all 3 extensions, that are somewhat linked together. All of them really have been created to address gaps in the OAuth2 standard.

But let me begin the story with a short reminder of OAuth’s authorisation code grant and the scopes.

Authorization code grant, scopes and access tokens

Wise man (thanks Jonathan!) asked me once, if I knew why OAuth was created. He quickly took me out of my misery and said:

For the purpose of delegation and remember – it’s all about the audience…

This was one of the most profound moments for me, when it actually started to sink into my thick skull why this identity standard works this particular way. Since then I observed what different organisations and vendors were doing to fulfil the requirements of, well… authorisation. I always said OAuth2==OAuth2, but OAuth2 by vendor A is not OAuth2 by vendor B. Standards are great, but a fair amount of functionality is delivered by implementation. As long as you’re compliant with the standards, you’re ok, right? At least on paper.

Let’s take a look at a simple example of logging into Strava (social media portal for athletes) with Facebook credentials. I open the login page and say, hey Strava, I’d like to use MY Facebook to log in. I have MY account there with MY email, first name, last name and a list of friends there. MY, MY, MY, what a selfish geek I am, but the truth of the matter is, I am the resource owner (email and phone number), Facebook is the authorisation server and Strava in this case is the relying party (client, though I hate this term, as it causes confusion with a user).

User (me in the browser), is redirected from Strava (client) to Facebook (Authorisation Server) and I am prompted for credentials. I successfully log in with my Password123 credential (they make you add a number now…) and Facebook asks me, if I would like to share MY email address and name with Strava. That consent is there, because Strava requested those details (claims) in the authorisation code request as a parameter called… scope. I click yes, without thinking and Facebook redirects me back to Strava with a code, which Strava exchanges with Facebook (behind my back…) for a token. What have I done? I have delegated the authority to fetch my details from Facebook to Strava. Strava trusts Facebook, so once it has the access token, that’s the proof I am who I say I am. The profound thing is that I didn’t have to share my credentials with Strava itself, pretty neat right?

Coarse vs fine grained authorisation

Since OAuth2 is an authorisation standard (yet it would seem it’s an authentication thingy), we now have the ability to protect, say API’s. Let’s look at the following example. A company is exposing all their API’s through a gateway, which is consuming tokens from an authorisation server. There are two endpoints, one for reading information and one for writing some data into the database, let’s call them api.1 and api.2. Since scopes are used to identify what information (resource) can be accessed, I go to the authorisation server to get a token with scopes api.1 and api.2. API gateway matches scopes api.1 with the endpoint api.1 (and api.2 respectively) allowing for the operation to be carried out. Coarse grained authorisation at its best.

The problem starts when, we look into finer details of api.2. I should be able to write data associated with my account, but not with my neighbour’s account, because of the requirement for data separation. This is a fine grained authorisation, which is incredibly difficult to achieve using scopes. The API endpoint now needs to check if that action can be carried out, but that means the security (authorisation in this case) is enforced in two places – at the API gateway level and then again in the application, though they are two completely different checks.

The common ‘abuse’ of scopes

Hold on… I don’t own api.1 and api.2, they are just my entitlements that I am allowed to consume. How does the above fit into my textbook example of OAuth2 between Strava and Facebook? Well, here’s the dilemma. It’s actually the authorisation server which now needs to make a decision whether the scope I asked for can be granted or not. But it’s still ok. Vendors solve this problem in many ways. PingFederate centrally manages scopes, which one can then assign to client id’s (applications). Forgerock’s AM doesn’t manage scopes centrally, but instead you define them at client id (application level). Everything is great for machine to machine account flows, because each service account is effectively an OAuth application and has its own client id and scopes assigned to it. Add a user context and the concept breaks down just like laws of physics inside black holes. We have 20k users who use the same client id (application) to obtain the token with appropriate scopes, which define the entitlements of the individual. The authorisation server now needs to do some more work, usually achievable through code or scripting to reach out to the directory server where the entitlements are usually stored and then match them with scopes. One could argue they’re not scopes anymore, but entitlements. User doesn’t own them, but has been allowed to use them. A neat access token would split the authorisation to make it look something like this:

{

“sub”:”user.0″,

“scope”:”API”,

“entitlement”:”api.1, api.2″,

}

This authorisation is still coarse grained at best. For Open Banking and many, many other use cases it’s just not enough and no one wants to make the decision twice. Why can’t we do it all in one go, adding fine grained authorisation concept into OAuth access tokens?

The RAR (Rich Authorization Request)

Introduces the request parameter ‘authorization_details’ that comprises of an array of JSON objects, containing authorisation parameters for a certain API or resource. The draft document gives this example:

[
      {
         "type": "account_information",
         "actions": [
            "list_accounts",
            "read_balances",
            "read_transactions"
         ],
         "locations": [
            "https://example.com/accounts"
         ]
      },
      {
         "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"
      }
   ]

We now have plenty of room to pass parameters that the authorisation server can parse to make a decision. In this case listing some account information and making a payment of EUR 123.50. Fine grained at its best and killing two birds with one stone.

The PAR (Pushed Authorization Request)

If you take a look at the above, it’s very clear it’s a lot of data. Imagine passing that as a parameter in the URL. You may come across the problem with some browsers, which limit the size of the URLs. Long story short PAR allows you to pass the whole request on the backchannel to the authorization server as a POST request, receive reference and use the reference to pass in the URL, in the browser. There’s more to PAR than just that use case, but you can read more detail here.

The JAR (JWT-secured Authorization Request)

First, let’s look at a client credentials grant. You can request the token and authorise the call via client_id and client_secret pair. You can also create a JWT, sign it with your private key and verify the call that way. Authorisation server is aware of your public key, checks the signature and there’s no need for the client_secret (let’s call it a passwordless client credentials grant).

With JAR, you can apply this logic into the authorization requests, too. All the parameters are contained within the signed JWT (scope, redirect uri including our new authorization_details parameter). This way you cannot tamper with it and it secures the weakest element (browser), too. One can go even further and… encrypt it (JWE). Some may think it’s an overkill, but it’s really, really, really secure.

You can pass it in the url as a parameter, or you can use PAR if it gets too big. An example request through standard url would look like this:

https://server.example.com/authorize?client_id=s6BhdRkqt3&
       request=eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ewogICAgImlzcyI6
       ICJzNkJoZFJrcXQzIiwKICAgICJhdWQiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBs
       ZS5jb20iLAogICAgInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsCiAg
       ICAiY2xpZW50X2lkIjogInM2QmhkUmtxdDMiLAogICAgInJlZGlyZWN0X3VyaSI6
       ICJodHRwczovL2NsaWVudC5leGFtcGxlLm9yZy9jYiIsCiAgICAic2NvcGUiOiAi
       b3BlbmlkIiwKICAgICJzdGF0ZSI6ICJhZjBpZmpzbGRraiIsCiAgICAibm9uY2Ui
       OiAibi0wUzZfV3pBMk1qIiwKICAgICJtYXhfYWdlIjogODY0MDAKfQ.Nsxa_18VU
       ElVaPjqW_ToI1yrEJ67BgKb5xsuZRVqzGkfKrOIX7BCx0biSxYGmjK9KJPctH1OC
       0iQJwXu5YVY-vnW0_PLJb1C2HG-ztVzcnKZC2gE4i0vgQcpkUOCpW3SEYXnyWnKz
       uKzqSb1wAZALo5f89B_p6QA6j6JwBSRvdVsDPdulW8lKxGTbH82czCaQ50rLAg3E
       YLYaCb4ik4I1zGXE4fvim9FIMs8OCMmzwIB5S-ujFfzwFjoyuPEV4hJnoVUmXR_W
       9typPf846lGwA8h9G9oNTIuX8Ft2jfpnZdFmLg3_wr3Wa5q3a-lfbgF3S9H_8nN3
       j1i7tLR_5Nz-g

Hope it’s now clear why we needed those extensions and why they come handy.