Pushed authorisation requests (PAR) in the OAuth 2.0 SDK

Posted 2019-10-26

The brand new Pushed Authorisation Requests (PAR) draft is now supported in v6.17 of the OAuth 2.0 / OpenID Connect SDK. PAR is a simple mean to up the security of authorisation requests, by making them integrity protected and confidential, and if the OAuth client has credentials - also authenticated.

The security of regular authZ requests

With regular OAuth 2.0 the client passes the request parameters encoded into the redirection URL that sends the user's browser (agent) for login and consent to the authorisation server.

GET /authz?response_type=code
 &client_id=123
 &state=ri4saex8
 &scope=upload_doc
 &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: c2id.com

Because the URL is submitted via the browser, the query parameters are exposed to the user and can potentially be tampered with. Applications therefore cannot rely on the authorisation request being authenticated, integrity protected or confidential. Do we have a solution for applications which require these security properties?

Securing authZ requests with JWT

The OAuth 2.0 extension for JWT-secured authorisation requests (JAR) allows the parameters to be authenticated and integrity protected by means of JWS. If JWE is applied to the digitally signed or HMAC-protected JWT, the request parameters become confidential. JAR has been a feature of OpenID Connect and is readily supported by the Connect2id server.

In order to secure authorisation requests with JAR the client needs to perform JOSE cryptography at the application layer. This is relatively easy with libraries such as Nimbus JOSE+JWT, but still, few developers take advantage of the extra security of JAR, unless the application is required to conform to a hardened OAuth 2.0 profile, such as FAPI.

PAR security works without application layer cryptography

The new PAR spec allows OAuth clients to submit their authorisation requests in a way that guarantees their integrity and confidentiality, without the need to deal with cryptography at the application layer. It also works for public clients without credentials.

PAR's solution is simple - a new endpoint at the authorisation server where clients can POST their authorisation request directly. If the client is confidential it will be required to authenticate as it does at the token endpoint.

Example HTTP POST of an authorisation request where the client authenticates with its client_secret:

POST /par HTTP/1.1
Host: c2id.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

response_type=code
&client_id=123
&state=ri4saex8
&scope=upload_doc
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

For a successful POST the server will return a handle, in the form of a URI, typically a URN, which is valid for some limited time and may be single use.

HTTP/1.1 201 Created
Cache-Control: no-cache, no-store
Content-Type: application/json

{
  "request_uri": "urn:ietf:params:oauth:request_uri:tioteej8",
  "expires_in": 60
}

The OAuth client then makes the actual authorisation request, passing the URI handle in the request_uri parameter. No other query parameters are needed!

GET /authz?request_uri=urn%3Aietf%3Aparams%3Aoauth%3Arequest_uri%3Atioteej8 HTTP/1.1
Host: c2id.com

The rest of the flow continues as usual for the requested response type.

No URL limits on the authZ request

PAR also removes potential size limitations on the authorisation request, to accommodate browsers which doesn't support long URLs.

What is the maximum length of a URL in different browsers?

PAR meshes nicely with JAR

The PAR endpoint accepts JARs submitted via the request parameter. An application which requires non-repudiation of the authorisation request can submit a signed JWT containing the parameters. On success the server will return the URI handle for the request_uri on the next step.

POST /par HTTP/1.1
Host: c2id.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3

request=eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ewogICAgImlzcyI6IC
JzNkJoZFJrcXQzIiwKICAgICJhdWQiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5j
b20iLAogICAgInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsCiAgICAiY2
xpZW50X2lkIjogInM2QmhkUmtxdDMiLAogICAgInJlZGlyZWN0X3VyaSI6ICJodHRw
czovL2NsaWVudC5leGFtcGxlLm9yZy9jYiIsCiAgICAic2NvcGUiOiAib3BlbmlkIi
wKICAgICJzdGF0ZSI6ICJhZjBpZmpzbGRraiIsCiAgICAibm9uY2UiOiAibi0wUzZf
V3pBMk1qIiwKICAgICJtYXhfYWdlIjogODY0MDAKfQ.Nsxa_18VUElVaPjqW_ToI1y
rEJ67BgKb5xsuZRVqzGkfKrOIX7BCx0biSxYGmjK9KJPctH1OC0iQJwXu5YVY-vnW0
_PLJb1C2HG-ztVzcnKZC2gE4i0vgQcpkUOCpW3SEYXnyWnKzuKzqSb1wAZALo5f89B
_p6QA6j6JwBSRvdVsDPdulW8lKxGTbH82czCaQ50rLAg3EYLYaCb4ik4I1zGXE4fvi
m9FIMs8OCMmzwIB5S-ujFfzwFjoyuPEV4hJnoVUmXR_W9typPf846lGwA8h9G9oNTI
uX8Ft2jfpnZdFmLg3_wr3Wa5q3a-lfbgF3S9H_8nN3j1i7tLR_5Nz-g

Making a PAR request with the OAuth 2.0 SDK

The OAuth 2.0 / OpenID Connect SDK was updated with new classes to support PAR:

Example usage:

// The PAR endpoint, obtained from AS metadata
URI endpoint = URI.create("https://c2id.com/par");

// Construct an OAuth 2.0 authorisation request as usual
AuthorizationRequest authzRequest = new AuthorizationRequest.Builder(
    new ResponseType("code"), clientID)
    .redirectURI(URI.create("https://example.com/cb"))
    .scope(new Scope("upload_doc"))
    .state(new State())
    .build();

// The client authenticates the same way as it's supposed at the
// token endpoint
ClientID clientID = new ClientID("123");
Secret clientSecret = new Secret("chele3faiYieNg4taoy8ingai3eili3b");
ClientAuthentication clientAuth = new ClientSecretBasic(clientID, clientSecret);

// Create the PAR request and POST it
HTTPRequest httpRequest = new PushedAuthorizationRequest(
    endpoint, clientAuth, authzRequest)
    .toHTTPRequest();
HTTPResponse httpResponse = httpRequest.send();

// Process the PAR response
PushedAuthorizationResponse response = PushedAuthorizationResponse.parse(httpResponse);

if (! response.indicatesSuccess()) {
    System.err.println("PAR request failed: " + response.getErrorObject().getHTTPStatusCode());
    System.err.println("Optional error code: " + response.getErrorObject().getCode());
    return;
}

PushedAuthorizationSuccessResponse successResponse = response.toSuccessResponse();
System.out.println("Request URI: " + successResponse.getRequestURI());
System.out.println("Request URI expires in: " + successResponse.getLifetime() + " seconds");

// Construct the authZ request for the browser, with request_uri as
// the sole parameter
URI redirectURI = new AuthorizationRequest.Builder(requestURI)
    .endpointURI(URI.create("https://c2id.com/authz"))
    .build()
    .toURI()

OAuth 2.0 SDK release notes

version 6.17 (2019-10-25)

  • Adds support for OAuth 2.0 Pushed Authorization Requests (draft-lodderstedt-oauth-par-00), see PushedAuthorizationRequest, PushedAuthorizationResponse, PushedAuthorizationSuccessResponse, PushedAuthorizationSuccessResponse and AuthorizationServerEndpointMetadata classes.
  • Deprecates RequestObjectPOST classes.
  • Updates ErrorObject to support URL-encoded parameters.
  • Adds new AuthenticationRequestDetector utility.