One of the biggest strengths of OIDC and OAuth is the usage of the browser front-channel. The browser can show a UI and follow redirects, this makes it very powerful and flexible.
Guess what – the biggest weakness of OIDC and OAuth is the browser front-channel. The browser can show a UI and follow redirects, this makes it very vulnerable.
Upon closer inspection, the weakest link (no pun intended) is really the authorize request and response. This is where very important parameters and data are being sent and received. The most common attacks are done by adversaries who can “read but not modify authorize requests and responses”. If they can also modify data and mount additional endpoints, it’s deadly.
In other words, the real question is, how can we retain the enormous advantages of the browser, while making authorize requests/responses more secure?
Plain OAuth 2.0 has nothing to offer here – so let’s start with OpenID Connect.
The “default” mode in OIDC is just like OAuth, where the authorize request parameters are transmitted using unprotected query string parameters – which means data can leak, and is not tamper-proof.
On the other hand, the authorize response in OIDC is always wrapped inside a signed JWT (called the identity token) – either on the browser front-channel in implicit/hybrid scenarios, or on the back channel for code flow.
OIDC also supports the concept of request objects, where the authorize request parameters are wrapped in a JWT, instead using plain query string parameters. Request objects can be transmitted either by value (on a request query string parameter or body field) or by reference using the request_uri parameter. This helps making the parameters tamper proof and adds all the features we like about security tokens like authentication, replay protection and expiration.
What about OAuth?
The above features do not exist in plain OAuth 2.0 – but can be added by the “aftermarket”. Currently specs are in the making to add JWT secured authorize requests (JAR) and responses (JARM). While some details are slightly different compared to the OIDC spec, the intentions are exactly the same – make them tamper proof (and sometimes also confidential).
Of course, this has some consequences. The client must be able to deal with the necessary crypto to create those JWTs, and URLs definitely get longer. Now mix in some more advanced scenarios like additional data in the JWTs (e.g. the new Rich Authorization Requests, aka RAR), URLs are not the optimal transmission vehicle anymore.
Enter by-reference request objects
With this technique the client creates the request object, stores it somewhere where the AS can “download” it, and then simply passes the URL on the request_uri parameter. Sounds easy, but has some real-world challenges, e.g. the AS needs to have access to the location, there needs to be some house-keeping around creating and deleting those files and SSRF (server-side request forgery) is definitely something you have to think about.
Which brings us to the (so far) last step in the evolution of “authorize request hardening” which called Pushed Authorization Requests (PAR). The idea is simple (and great) – basically the AS also acts as a “drop location” for by-reference request objects.
With PAR, the client first pushes all the authorize request parameters via a back-channel connection to the authorization server. This call can utilize all the benefits of back-channels (e.g. strong client authentication or simply no being a front-channel). The AS will then respond with a URI that represents the request parameters (and a time-out).
The front-channel authorize request would then be reduced to:
Thorsten wrote really good blog posts that go into more detail – check them out here and here.
What does that mean for IdentityServer users?
OIDC request objects are supported since version v3. JARs have slightly different validation rules and will be supported in v4. For PAR, we are looking for a sponsor right now, if your company wants that feature, let us know!