You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 278e641
Browse filesBrowse the repository at this point in the historyBrowse files
Redeem a pushed authorization request with its pushed parameters only
A PAR request_uri used at the authorization endpoint was array_merged
over the parameters sent with it: a pushed parameter won, but one the
pushed request did not carry was taken from the front channel, so the
user agent could add prompt, max_age, acr_values, claims, nonce,
response_mode, state, code_challenge, dpop_jkt, issuer_state,
authorization_details and others to a pushed request. RFC 9126 section 4
has the request built as RFC 9101 defines, whose sections 5 and 6.3 have
the authorization server use only the request's own parameters, even
where the client repeats them in the query.
- RequestParamsResolver redeems a PAR request_uri with the pushed
parameters, plus the client_id (the pushed one wins, RequestUriRule
refuses another client) and the request_uri sent with it, whatever
else the request carries. Every other front-channel parameter is
ignored, whether or not PAR is required. RequestUriRule logs at notice
the names of ignored parameters the pushed request does not carry.
Request Objects by value and by https request_uri keep the OpenID
Connect Core merge.
- The token and end session endpoints mark the requests they hand on
(RequestParamsResolver::ATTRIBUTE_OWN_PARAMS_ONLY), whose parameters
are then read as they were sent: a request or request_uri parameter
there is ignored (RFC 6749 section 3.2), where a Request Object's
claims used to be merged over them since v6.
- The authproc state gets the authorization request as the grants
validate it (the query of a GET, the body of a POST).
- The PAR endpoint persists what its rules validated: with a Request
Object, the form body merged with the Request Object claims (which
win), rather than the payload alone, which dropped a validated form
parameter. A push without response_type is refused with
invalid_request, since nothing can supply it at redemption.
- Docs: the PAR endpoint section and the plain OAuth 2.0 notes in
docs/3, the v7 PAR entry and a low-impact note in docs/6, and the
RFC 9126 compliance matrix (rows 5, 12, 16 and 17 updated, row 23
added).
| 3 | Authenticate the client the same way as at the token endpoint (§2.1, step 1). |`PushedAuthorizationController::__invoke()` via `AuthenticatedOAuth2ClientResolver::forAnySupportedMethod()`; confidential clients must authenticate. |`PushedAuthorizationControllerTest`: `testClientAuthenticationFailureThrows`, `testConfidentialClientMustAuthenticate`. |
26
26
| 4 | MUST reject the request if the `request_uri` parameter is provided (§2.1 step 2; §2.1-1 "MUST NOT be provided"). |`PushedAuthorizationController::__invoke()` rejects `request_uri` in the body. |`PushedAuthorizationControllerTest::testRejectsRequestUriInBody`. |
27
-
| 5 | MUST validate the pushed request as it would an authorization request at the authorization endpoint (§2.1 step 3). |`PushedAuthorizationController::__invoke()` runs the authorization request-rule pipeline (`StateRule`, `ClientRedirectUriRule`, `RequestObjectRule`, `ResponseModeRule`, `ScopeRule`, `RequiredOpenIdScopeRule`, `CodeChallengeRule`, `CodeChallengeMethodRule`). |`PushedAuthorizationControllerTest::testHandlesValidParRequest` (and the rule-specific tests for each rule). |
27
+
| 5 | MUST validate the pushed request as it would an authorization request at the authorization endpoint (§2.1 step 3). |`PushedAuthorizationController::__invoke()` runs the authorization request-rule pipeline (`StateRule`, `ClientRedirectUriRule`, `RequestObjectRule`, `ResponseModeRule`, `ScopeRule`, `RequiredOpenIdScopeRule`, `CodeChallengeRule`, `CodeChallengeMethodRule`, `DpopJktRule`, `IssuerStateRule`, `AuthorizationDetailsRule`, `OfferedCredentialsRule`); `resolveParametersToPersist()` refuses a request without `response_type`, which nothing could supply at redemption (row 23). |`PushedAuthorizationControllerTest::testHandlesValidParRequest`, `testRefusesAPushedRequestWithoutAResponseType` (and the rule-specific tests for each rule). |
28
28
29
29
## Successful response (RFC 9126 §2.2)
30
30
@@ -46,7 +46,7 @@ When you change PAR / request_uri behaviour, keep this table in sync.
46
46
47
47
| # | Requirement (spec ref) | Enforced in | Covered by test |
| 12 | When a Request Object is used, all authorization request parameters MUST appear as claims of the JWT; only the (validated) payload is used (§3-1). |`PushedAuthorizationController::resolveParametersToPersist()` persists the Request Object payload only (drops body params) when JAR is used. |`PushedAuthorizationControllerTest::testPersistsRequestObjectPayloadOnlyWhenJarIsUsed`. |
49
+
| 12 | When a Request Object is used, all authorization request parameters MUST appear as claims of the JWT (§3-1; a requirement on the client). | Lenient: the rules validate the Request Object together with the form body, its claims superseding form parameters of the same name (`RequestParamsResolver`), and`PushedAuthorizationController::resolveParametersToPersist()` persists that set, without the client authentication parameters and `request`. A compliant client is unaffected; one which leaves parameters in the form body is redeemed with what was validated (the redemption reads nothing else, row 23). |`PushedAuthorizationControllerTest`: `testPersistsTheFormBodyWithTheRequestObjectClaimsSupersedingItWhenJarIsUsed`, `testPersistsTheFormBodyScopeWithARequestObjectWhichLeavesItOut`. |
50
50
| 13 | MUST validate the Request Object signature (§3 step 2). |`RequestObjectRule::verifySignature()` (JWKS via `JwksResolver`). |`RequestObjectRuleTest`: `testThrowsForInvalidRequestObject`, `testReturnsValidRequestObject`, `testReturnsValidJarRequestObjectForOAuth2Request`, `testMissingClientJwksThrows`. |
51
51
| 14 | If the client has credentials, MUST reject when the authenticated `client_id` does not match the `client_id` claim in the Request Object (§3 step 3). |`PushedAuthorizationController::resolveParametersToPersist()` (claim vs authenticated client); `RequestObjectRule` (JAR `client_id` claim vs client). |`PushedAuthorizationControllerTest::testRejectsRequestObjectClientIdClaimWhichDoesNotMatchAuthenticatedClient`; `RequestObjectRuleTest::testThrowsForOAuth2RequestWithMismatchedClientIdClaim`. |
52
52
@@ -55,9 +55,10 @@ When you change PAR / request_uri behaviour, keep this table in sync.
55
55
| # | Requirement (spec ref) | Enforced in | Covered by test |
| 15 | An expired `request_uri` MUST be rejected (§4-3). |`RequestUriRule` checks `isExpired()` (UTC); `PushedAuthorizationRequestRepository::findValid()` also filters expired. |`RequestUriRuleTest::testThrowsIfPushedAuthorizationRequestIsExpired`; `PushedAuthorizationRequestRepositoryTest::testFindValidReturnsNullForExpiredRequestUri`. |
58
-
| 16 |`request_uri` is treated as one-time use (§4-3 SHOULD; implemented strictly). |`RequestUriRule`consumes the `request_uri` atomically at validation time; `PushedAuthorizationRequestRepository::consume()` is an atomic `UPDATE … WHERE is_consumed = 0` (replay guard). |`RequestUriRuleTest`: `testThrowsIfPushedAuthorizationRequestIsConsumed`, `testThrowsIfPushedAuthorizationRequestConsumptionFails`, `testCanUseValidPushedAuthorizationRequestUri`; `PushedAuthorizationRequestRepositoryTest`: `testConsumeReturnsTrueOnlyOnce`, `testFindValidReturnsNullForConsumedRequestUri`, `testConsumeInvalidatesCache`. |
59
-
| 17 | The AS MUST validate authorization requests arising from a pushed request as it would any other (§4-4). |`RequestUriRule` resolves the pushed params into the merged request view, after which the standard authorization rule pipeline runs. |`RequestUriRuleTest::testCanUseValidPushedAuthorizationRequestUri` (+ the full rule pipeline). |
58
+
| 16 |`request_uri` is treated as one-time use (§4-3 SHOULD; implemented strictly). |`RequestUriRule`refuses a consumed `request_uri`; `AuthorizationServer::completeAuthorizationRequest()` consumes it when the authorization response is issued (the user agent comes back with it after the login, so it can not be consumed at validation), refusing the response if it was consumed meanwhile; `PushedAuthorizationRequestRepository::consume()` is an atomic `UPDATE … WHERE is_consumed = 0` (replay guard). |`RequestUriRuleTest`: `testThrowsIfPushedAuthorizationRequestIsConsumed`, `testCanUseValidPushedAuthorizationRequestUriWithoutConsumingIt`; `AuthorizationServerTest`: `testConsumesThePushedRequestBeforeTheResponseIsIssued`, `testRefusesToCompleteAPushedRequestWhichIsAlreadyConsumed`; `PushedAuthorizationRequestRepositoryTest`: `testConsumeReturnsTrueOnlyOnce`, `testFindValidReturnsNullForConsumedRequestUri`, `testConsumeInvalidatesCache`. |
59
+
| 17 | The AS MUST validate authorization requests arising from a pushed request as it would any other (§4-4). |`RequestParamsResolver` gives the rules the pushed params as the request's (row 23), after which the standard authorization rule pipeline runs on them. |`RequestUriRuleTest::testCanUseValidPushedAuthorizationRequestUriWithoutConsumingIt` (+ the full rule pipeline). |
60
60
| 18 | If policy requires PAR (global or per-client), the AS MUST refuse, with `invalid_request`, any authorization request without a PAR `request_uri` (§4-5). |`RequestUriRule` PAR-required check (`getRequirePushedAuthorizationRequests()` module + client). |`RequestUriRuleTest`: `testThrowsIfParIsRequiredGloballyButNotUsed`, `testThrowsIfParIsRequiredForClientButNotUsed`. |
61
+
| 23 | The client builds the authorization request with the `request_uri` as RFC 9101 defines (§4-1), and the AS MUST only use the parameters of the request, even where the client repeats them in the query (RFC 9101 §5, §6.3). | `RequestParamsResolver::resolveParams()` redeems a PAR `request_uri` with the pushed params only; of the front-channel params it keeps `client_id` (the pushed one supersedes it, and `RequestUriRule` refuses one naming another client) and `request_uri`, and ignores every other one, repeated or not. `RequestUriRule` logs at notice the names of ignored params the pushed request does not carry. A `request_uri` sent with `request` is redeemed the same way, whatever the `request` value (null included), and `RequestUriRule` refuses the two together. The token and end session endpoints mark their requests (`RequestParamsResolver::ATTRIBUTE_OWN_PARAMS_ONLY`), so a `request_uri` sent there is ignored rather than redeemed (RFC 6749 §3.2). | `RequestParamsResolverTest`: `testRedeemsAPushedAuthorizationRequestUriWithThePushedParamsOnly`, `testIgnoresParamsSentWithAPushedRequestUriWhichThePushedRequestLacks`, `testAPushedClientIdSupersedesTheOneSentWithTheRequestUri`, `testGetAllResolvesOnlyTheClientIdAndRequestUriForInvalidPushedAuthorizationRequestUri`, `testRedeemsAPushedRequestUriWithThePushedParamsOnlyNextToARequestParam`, `testReadsTheOwnParamsOnlyOfARequestMarkedSoWhateverRequestUriItCarries`; `AccessTokenControllerTest::testHandsARequestWithoutAProofToTheGrantsReadAsItWasSent`; `EndSessionControllerTest::testValidatesTheLogoutRequestReadAsItWasSent`; `RequestUriRuleTest`: `testLogsTheNamesOfParamsSentWithAPushedRequestUriWhichThePushedRequestLacks`, `testLogsNothingForParamsThePushedRequestRepeats`. |
0 commit comments