Announcing Support for Pushed Authorization Requests (PAR) in IdentityServer v7
OAuth and OpenID Connect requests consist of two steps. The front channel for user interactions like login or consent, and the back channel for transmitting the resulting tokens into client client applications.
Front channel requests are done via the browser to the so called authorize endpoint, and the way the protocol was designed, those requests typically contain a significant number of parameters to help the authorization server to optmize the user workflow. Typical parameters would be the client ID, redirect URIs, scopes and more, e.g.:
/authorize?
client_id=web&
redirect_uri=https://myapp.com/callback&
response_type=code&
response_mode=query&
scope=openid profile api1 api2&
state=xyz&
code_challenge=xyz&
ui_locales=en-US
Those parameters need to be carefully validated by the authorization server, and to make this even more complex, the client (and maybe also the user) is anonymous at this point.
Historically, many attacks against OAuth-based systems took advantage of bugs in that validation logic by manipulating the parameters (and their combination) on the authorize request. Probably the most prominent one is tampering with the redirect URI (see here).
Last but not least, the fact that you can package up the complete authorize request with all its (manipulated) parameters opens up the door to all kinds of click/phishing attacks.
Countermeasures
To improve that situation, a couple of different approaches could be implemented, e.g.:
- remove those parameters from the authorize request
- make the parameters tamper proof
- authenticate the client at the authorize endpoint to help validation
The OpenID Connect Request Object and later the JWT-secured Authorization Request (JAR) specification achieved goals 2 and 3 by wrapping the request parameters in a signed JWT data structure:
GET /authorize?client_id=client&request=
eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyYmRjIn0.ewogICAgImlzcyI6ICJzNkJoZF
JrcXQzIiwKICAgIC.JhdWQiOiAiaHR0cHM6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iL
Aog ICAgInJlc3BvbnNlX3R5cGUiOiAiY29kZSBpZF90b2tlbiIsCiAgICAiY2xpZW
50X2 lkIjogInM2QmhkUmtxdDMiLAogICAgInJlZGlyZWN0X3VyaSI6ICJodHR
wczovL2Ns aWVudC5leGFtcGxlLm9yZy9jYiIsCiAgICAic2NvcGUiOiAib3Blbml
kIiwKICAgIC JzdGF0ZSI6ICJhZjBpZmpzbGRraiI.sCiAgICAibm9uY2UiOiAibi0wU
zZfV3pBMk1q IiwKICAgICJtYXhfYWdlIjogODY0MDAKfQ.Nsxa_18VUElVaPjqW
_ToI1yrEJ67BgK b5xsuZRVqzGkfKrOIX7BCx0biSxYGmjK9KJPctH1OC0iQJwXu5Y
The request parameter contains the base64 URL encoded JWT:
{
"typ": "JWT",
"alg": "RS256",
"kid": "1"
}.
{
"iss": "client",
"aud": "https://authorizationserver.com",
"response_type": "code",
"client_id": "client",
"redirect_uri": "https://myapp.com/cb",
"scope": "openid customer.api",
"state": "abc",
"code_challenge": "def"
}.
[Signature]
This makes the parameters tamper proof and by way of the applied signature allows authenticating the client. IdentityServer has had support for JAR for a couple of years now.
The downside of JAR is the increased complexity in clients around cryptography/key management and JWT creation as well as the increased size of URLs which might be a problem for certain environments.
The JAR spec also introduced the request_uri parameter to offload the actual JWT, but this came with its own set of challenges.
Pushed Authorization Requests (PAR)
The PAR spec is a continuation that actually addresses all three aforementioned goals and also provides a formalized implementation of the request_uri mechanism. It is actually astonishingly simple. A typical code flow request now works like this:
- the client sends all authorize request parameters to a special endpoint at the authorization server. This endpoint is authenticated using the client ID/secret mechanism. The endpoint returns an identifier that represents the request parameters.
POST /par
client_id=client&
response_type=code&
redirect_uri=https://myapp.com/cb&
state=abc&
code_challenge=def
- the client now does a normal authorize request, but instead of sending the parameters, it sends the returned identifier from step 1
GET /authorize?client_id=client&request_uri=id
- the authorization server returns the normal code response, and the client can retrieve the tokens from the token endpoint
This way the client can be authenticated before the authorize request, the parameters are not sent via the (fragile) browser front channel, and the PAR implementation at the authorization server can apply a number of additional checks like time-to-live, single use only request IDs and advanced validation.
This mechanism present a very good complexity to security ratio and increases the robustness of the front channel tremendously. Since this eliminates a whole class of attacks against OAuth, we think PAR should become the standard mechanism for authorize requests.
We are happy to announce that starting with IdentityServer v7, we have a fully spec compliant PAR implementation.
We have a preview docs here as well as as a sample.