Pushed Authorization Requests Draft adopted by OAuth Working Group

Torsten Lodderstedt
OAuth 2
Published in
4 min readJan 9, 2020

The OAuth Working Group recently adopted the Pushed Authorization Requests (PAR) draft as working group document, which is an important step on its way to become an RFC. PAR improves the robustness and security of the OAuth code flow in a very simple way.

Instead of sending all authorization request parameters through the browser, the client first pushes the payload of an authorization request to the authorization server via a direct request and is provided with a request URI that is used as reference to the data in the subsequent authorization request.

Background

Initiating an OAuth authorization is simple. Just build a URL with a few parameters and open it in a browser (or send the user to this destination via HTTP redirect). Its simplicity is one of the cornerstones of the success of OAuth.

But there is no light without shadow and experience has revealed several drawbacks that PAR aims to solve.

URL Length

The length of URLs has limits in browsers and web servers. If the value of request parameters, such as “scope”, “redirect_uri”, or “claims” (from OpenID Connect), gets too long, the authorization process might break.

“claims”, for example, is used to request individual data elements about the user, so-called Claims, using a JSON structure. That’s a great way to exchange identity data in a privacy preserving way. But it might also result in very big request URLs and cause unexpected browser behavior.

Let’s illustrate this using an example. This request

https://as.example.com/authorize?response_type=code&
state=af0ifjsldkj&
client_id=s6BhdRkqt3&
redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb&
code_challenge=K2-ltc83acc4h0c9w6ESC_rEMTJ3bww-uCHaoeK1t8U&
code_challenge_method=S256&
scope=openid&
claims=%7B%22userinfo%22%3A%7B%22nickname%22%3Anull%2C%22email%22%3A%7B%22essential%22%3Atrue%7D%2C%22email%5Fverified%22%3A%7B%22essential%22%3Atrue%7D%2C%22picture%22%3Anull%2C%22verified%5Fclaims%22%3A%7B%22verification%22%3A%7B%22trust%5Fframework%22%3A%7B%22value%22%3A%22eidas%5Fial%5Fsubstantial%22%7D%2C%22evidence%22%3A%5B%7B%22type%22%3A%7B%22value%22%3A%22id%5Fdocument%22%7D%2C%22document%22%3A%7B%22type%22%3A%7B%22values%22%3A%5B%22idcard%22%2C%22passport%22%5D%7D%7D%7D%5D%7D%2C%22claims%22%3A%7B%22given%5Fname%22%3Anull%2C%22family%5Fname%22%3A%7B%22value%22%3A%22Meier%22%7D%2C%22birthdate%22%3Anull%2C%22place%5Fof%5Fbirth%22%3Anull%2C%22nationality%22%3Anull%2C%22address%22%3Anull%7D%7D%7D%2C%22id%5Ftoken%22%3A%7B%22acr%22%3A%7B%22values%22%3A%5B%22urn%3Amace%3Aincommon%3Aiap%3Asilver%22%5D%7D%7D%7D

uses the “claims” parameter to asks the authorization server (in this case acting as OpenID OP) to provide several user claims, some of them as verified claims according to OpenID Connect for Identity Assurance. Here is the decoded “claims” content:

{ 
"userinfo":{
"nickname":null,
"email":{
"essential":true
},
"email_verified":{
"essential":true
},
"picture":null,
"verified_claims":{
"verification":{
"trust_framework":{
"value":"eidas_ial_substantial"
},
"evidence":[
{
"type":{
"value":"id_document"
},
"document":{
"type":{
"values":[
"idcard",
"passport"
]
}
}
}
]
},
"claims":{
"given_name":null,
"family_name":{
"value":"Meier"
},
"birthdate":null,
"place_of_birth":null,
"nationality":null,
"address":null
}
}
},
"id_token":{
"acr":{
"values":[
"urn:mace:incommon:iap:silver"
]
}
}
}

We cannot reduce the payload, but we can solve the URL length problem by not sending the data through the browser.

With PAR, the client first sends a direct POST request to the authorization server and “deposits” the request data with the authorization server.

POST /as/par HTTP/1.1
Host: as.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3
response_type=code&
state=af0ifjsldkj&
client_id=s6BhdRkqt3&
redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb&
code_challenge=K2-ltc83acc4h0c9w6ESC_rEMTJ3bww-uCHaoeK1t8U&
code_challenge_method=S256&
scope=openid&
claims=%7B%22userinfo%22%3A%7B%22nickname%22%3Anull%2C%22email%22%3A%7B%22essential%22%3Atrue%7D%2C%22email%5Fverified%22%3A%7B%22essential%22%3Atrue%7D%2C%22picture%22%3Anull%2C%22verified%5Fclaims%22%3A%7B%22verification%22%3A%7B%22trust%5Fframework%22%3A%7B%22value%22%3A%22eidas%5Fial%5Fsubstantial%22%7D%2C%22evidence%22%3A%5B%7B%22type%22%3A%7B%22value%22%3A%22id%5Fdocument%22%7D%2C%22document%22%3A%7B%22type%22%3A%7B%22values%22%3A%5B%22idcard%22%2C%22passport%22%5D%7D%7D%7D%5D%7D%2C%22claims%22%3A%7B%22given%5Fname%22%3Anull%2C%22family%5Fname%22%3A%7B%22value%22%3A%22Meier%22%7D%2C%22birthdate%22%3Anull%2C%22place%5Fof%5Fbirth%22%3Anull%2C%22nationality%22%3Anull%2C%22address%22%3Anull%7D%7D%7D%2C%22id%5Ftoken%22%3A%7B%22acr%22%3A%7B%22values%22%3A%5B%22urn%3Amace%3Aincommon%3Aiap%3Asilver%22%5D%7D%7D%7D

That’s much more robust since there is no practical limit on the request size for POST requests.

Note: The parameter encoding stays the same as for the authorization URL, which makes switching to PAR a simple task.

The AS responds with a request URI,

HTTP/1.1 201 Created
Cache-Control: no-cache, no-store
Content-Type: application/json
{
"request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
"expires_in": 90
}

which is used by the client in the subsequent authorization request as follows,

GET /authorize?request_uri=
urn%3Aexample%3Abwc4JK-ESC0w8acc191e-Y1LTC2 HTTP/1.1

This approach also solves further challenges.

Client Authentication

Have you ever noticed that a OAuth client is not actually authenticated before the authorization process with the user starts? The authorization request just contains the claimed “client_id” of the caller. Even if the authorization server displays information about the client and asks for consent, it cannot be sure it’s actually the legitimate client that sent the request. This authentication happens in the next step, when the client exchanges the authorization code for an access token at the token endpoint.

Quite late, isn’t it?

With PAR, confidential clients are authenticated when sending the pushed authorization request. Since this happens before the actual authorization process is started, the authorization server can refuse unauthorized or mal-formed requests early and has much more confidence in the client’s identity in the authorization process (without the need to sign authorization requests).

Request integrity and confidentiality

As the authorization request is normally sent in the clear through the browser, the user (as an attacker) can inspect and modify the request. Although tampering could be detected later on if the client would check the process results properly, e.g. the scope of the access token, it is better to cryptographically protect the authorization request.

PAR provides this protection by using the HTTPS/TLS-protected channel between client and authorization server without the need for application level cryptography.

Conclusion

PAR significantly improves robustness and security of the OAuth code flow in a very simple way. The client just needs to be modified to send the request data (with the same encoding) directly to the authorization server and to invoke the authorization process using the issued request URI. That‘s it.

If the client wants extra application level security, it can use PAR in conjunction with signed (and encrypted) request objects.

Do you want to give it a try?

PAR is already implemented in several products & projects, including:

If you want to contribute to the further evolution of PAR, please visit the OAuth mailing list.

--

--

Torsten Lodderstedt
OAuth 2

Torsten is CTO@yes.com, software architect with strong security interest, identity nerd, contributor to OAuth, OpenID, Open Banking & Electronic Signatures