Allow ClientAssertions with PAR#340
Allow ClientAssertions with PAR#340Erwinvandervalk wants to merge 2 commits intofix/dpop-stale-client-assertion-1392from
Conversation
3774a79 to
98330c2
Compare
| var proof = await _dPoPProofService.CreateProofTokenAsync(new DPoPProofRequest | ||
| { | ||
| Url = url, | ||
| Method = HttpMethod.Get, | ||
| DPoPProofKey = key, | ||
| AccessToken = token.AccessToken, | ||
| }); |
There was a problem hiding this comment.
Shouldn't access token management create this proof? I think there's an http handler designed for interaction with the resource server.
| // --- DPoP thumbprint --- | ||
| var dPoPKeyStore = context.HttpContext.RequestServices.GetRequiredService<IDPoPKeyStore>(); | ||
| var key = await dPoPKeyStore.GetKeyAsync(ClientName); | ||
| if (key != null) | ||
| { | ||
| var jkt = dPoPProofService.GetProofKeyThumbprint(key.Value); | ||
| if (jkt != null) | ||
| { | ||
| context.Properties.SetProofKey(key.Value); | ||
| context.ProtocolMessage.Parameters[OidcConstants.AuthorizeRequest.DPoPKeyThumbprint] = | ||
| jkt.ToString(); | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't think we need to add this. We should double check, but the backchannel handler already sets the DPoP header value.
DPoP defines two ways to bind the authorization code to the proof key. If you're not using PAR, you have to use the dpop_jkt query string parameter. Probably this is so because custom headers on a redirect can be annoying. So, the spec gives you this easy way to say "here's the thumbprint of my public key".
But, if you are using PAR, the backchannel pushed authorization request can easily set headers, including the DPoP header. That header is actually a stronger binding because it conveys both the public key and proof of possession of the private key.
The spec also points out that doing it this way means that the client can just always include the proof on all backchannel requests, and needs less special case logic.
For your reference, from RFC 9449:
When Pushed Authorization Requests (PARs) [RFC9126] are used in conjunction with DPoP, there are two ways in which the DPoP key can be communicated in the PAR request:
The dpop_jkt parameter can be used as described in Section 10 to bind the issued authorization code to a specific key. In this case, dpop_jkt MUST be included alongside other authorization request parameters in the POST body of the PAR request.
Alternatively, the DPoP header can be added to the PAR request. In this case, the authorization server MUST check the provided DPoP proof JWT as defined in Section 4.3. It MUST further behave as if the contained public key's thumbprint was provided using dpop_jkt, i.e., reject the subsequent token request unless a DPoP proof for the same key is provided. This can help to simplify the implementation of the client, as it can "blindly" attach the DPoP header to all requests to the authorization server regardless of the type of request. Additionally, it provides a stronger binding, as the DPoP header contains a proof of possession of the private key.
Problem statement
When the authorization server supports Pushed Authorization Requests (PAR), the ASP.NET Core OIDC handler makes a backchannel POST to the PAR endpoint before redirecting the user.
ConfigureOpenIdConnectOptionsdoes not wrap theOnPushAuthorizationevent, so:IClientAssertionServiceis never called for PAR requests — only for code exchange (OnAuthorizationCodeReceived).dpop_jktparameter is not included in the PAR request. It's currently only added duringOnRedirectToIdentityProvider, but when PAR is active the authorize parameters are sent in the backchannel PAR request, not the front-channel redirect.This means clients using
private_key_jwtauthentication cannot use PAR — the authorization server rejects the unauthenticated PAR request.Per RFC 9126 §2, the PAR endpoint requires client authentication using the same method as the token endpoint:
The
WebJarJwtsample works around this with an explicit opt-out:Solution
Add a handler for
options.Events.OnPushAuthorizationand calculate both a dpop proof and add the jwt.