[java-idp-oidc] branch maint-4.3 updated: JOIDC-288 - Pushed Authorization Requests (PAR) do not support claims parameter
Codeberg
noreply at shibboleth.net
Tue Sep 1 16:10:37 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch maint-4.3
in repository java-idp-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-oidc/commit/98e075ea64aec93229a3a97e2c8c7983caf42aef
The following commit(s) were added to refs/heads/maint-4.3 by this push:
new 98e075ea JOIDC-288 - Pushed Authorization Requests (PAR) do not support claims parameter
98e075ea is described below
commit 98e075ea64aec93229a3a97e2c8c7983caf42aef
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Sep 1 19:04:51 2026 +0300
JOIDC-288 - Pushed Authorization Requests (PAR) do not support claims parameter
https://shibboleth.atlassian.net/browse/JOIDC-288
- Modified the DefaultRequestedClaimsLookupFunction to parse OIDCClaimsRequest from String when the request object is produced by PAR
- The change is covered both in unit and flow tests
---
.../DefaultRequestedClaimsLookupFunction.java | 8 +++-
.../DefaultRequestedClaimsLookupFunctionTest.java | 56 ++++++++++++++++++++++
.../op/profile/flow/PushedAuthorizeFlowTest.java | 55 +++++++++++++++++++--
3 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunction.java
index 0619e900..9a24a4a7 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunction.java
@@ -56,11 +56,15 @@ public class DefaultRequestedClaimsLookupFunction
if (requestObject != null && requestObject.getJWTClaimsSet().getClaim("claims") != null) {
final Object claims = requestObject.getJWTClaimsSet().getClaim("claims");
if (claims instanceof Map) {
- log.debug("claims claim is a map, converting it into a JSONObject");
+ log.debug("claims claim is a map, converting it into a OIDCClaimsRequest");
// the casting is safe as Nimbus shouldn't allow other than String-keyed maps to exist here
return OIDCClaimsRequest.parse(new JSONObject((Map<String, ?>) claims));
+ } else if (isRequestObjectFromPar && claims instanceof String string){
+ log.debug("claims claim is a string from PAR, converting it into a OIDCClaimsRequest");
+ return OIDCClaimsRequest.parse(string);
} else {
- log.error("claims claim is not of expected type (java.util.Map), it's: {}", claims.getClass());
+ log.error("claims claim is not of expected type (Map or String from PAR), it's: {}, from PAR: {}",
+ claims.getClass(), isRequestObjectFromPar);
return null;
}
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunctionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunctionTest.java
index 2611ce1f..6cbd6fad 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunctionTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/messaging/context/navigate/DefaultRequestedClaimsLookupFunctionTest.java
@@ -90,4 +90,60 @@ public class DefaultRequestedClaimsLookupFunctionTest extends AbstractAuthentica
assert result != null;
Assert.assertEquals(crRequestObject.toJSONObject(), result.toJSONObject());
}
+
+ @Test
+ public void testSuccessReqObject_stringValueNotFromPar() throws ParseException {
+ final ClaimsSetRequest idTokenClaims = new ClaimsSetRequest()
+ .add(new ClaimsSetRequest.Entry(IDTokenClaimsSet.SUB_CLAIM_NAME)
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL));
+ final OIDCClaimsRequest crParameter = new OIDCClaimsRequest()
+ .withIDTokenClaimsRequest(idTokenClaims);
+
+ final ClaimsSetRequest userInfoClaims = new ClaimsSetRequest()
+ .add(new ClaimsSetRequest.Entry(UserInfo.BIRTHDATE_CLAIM_NAME)
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL));
+ final OIDCClaimsRequest crRequestObject = new OIDCClaimsRequest()
+ .withIDTokenClaimsRequest(idTokenClaims)
+ .withUserInfoClaimsRequest(userInfoClaims);
+
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder().claim("claims", crRequestObject.toJSONString()).build();
+ final AuthenticationRequest req = new AuthenticationRequest.Builder(
+ new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+ URI.create("https://example.com/callback")).claims(crParameter)
+ .requestObject(PlainJWT.parse(new PlainJWT(ro).serialize())).state(new State()).build();
+ // request object JWT is serialized and parsed in order to simulate incoming authentication request better
+ msgCtx.setMessage(req);
+ oidcCtx.setRequestObject(req.getRequestObject());
+ Assert.assertNull(lookup.apply(prc));
+ }
+
+ @Test
+ public void testSuccessReqObject_stringValueFromPar() throws ParseException {
+ final ClaimsSetRequest idTokenClaims = new ClaimsSetRequest()
+ .add(new ClaimsSetRequest.Entry(IDTokenClaimsSet.SUB_CLAIM_NAME)
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL));
+ final OIDCClaimsRequest crParameter = new OIDCClaimsRequest()
+ .withIDTokenClaimsRequest(idTokenClaims);
+
+ final ClaimsSetRequest userInfoClaims = new ClaimsSetRequest()
+ .add(new ClaimsSetRequest.Entry(UserInfo.BIRTHDATE_CLAIM_NAME)
+ .withClaimRequirement(ClaimRequirement.ESSENTIAL));
+ final OIDCClaimsRequest crRequestObject = new OIDCClaimsRequest()
+ .withIDTokenClaimsRequest(idTokenClaims)
+ .withUserInfoClaimsRequest(userInfoClaims);
+
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder().claim("claims", crRequestObject.toJSONString()).build();
+ final AuthenticationRequest req = new AuthenticationRequest.Builder(
+ new ResponseType("code"), new Scope("openid"), new ClientID("000123"),
+ URI.create("https://example.com/callback")).claims(crParameter)
+ .requestObject(PlainJWT.parse(new PlainJWT(ro).serialize())).state(new State()).build();
+ // request object JWT is serialized and parsed in order to simulate incoming authentication request better
+ msgCtx.setMessage(req);
+ oidcCtx.setRequestObject(req.getRequestObject());
+ oidcCtx.setRequestObjectFromPar(true);
+ final OIDCClaimsRequest result = lookup.apply(prc);
+ assert result != null;
+ Assert.assertEquals(crRequestObject.toJSONObject(), result.toJSONObject());
+ }
+
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
index ac83b90b..9029ed49 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
@@ -48,6 +48,9 @@ import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
+import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriDeserializationFunction;
import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
@@ -193,6 +196,26 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
}
}
+ @Test
+ public void testSuccessWithOpenIdParams() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException {
+ for (final String clientId : clientIds) {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final var requestParameters = createRequestParameters(clientId);
+ final String nonce = idGenerator.generateIdentifier();
+ requestParameters.put("nonce", nonce);
+ requestParameters.put("claims",
+ "{\"id_token\":{\"acr\":{\"essential\":true,\"values\":[\"password\"]}}}");
+ setHttpFormRequest("POST", requestParameters);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString(), null, nonce, "password");
+ }
+ }
+
@Test
public void testFailure_unregisteredScope() throws IOException, NoSuchAlgorithmException, URISyntaxException, DataSealerException,
ComponentInitializationException {
@@ -886,18 +909,33 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
}
protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt) {
+ verifyAuthorizeEndpoint(clientId, requestUri, jkt, null);
+ }
+
+ protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt,
+ final String nonce) {
+ verifyAuthorizeEndpoint(clientId, requestUri, jkt, nonce, null);
+ }
+
+ protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt,
+ final String nonce, final String essentialAcr) {
initializeMocks();
initializeThreadLocals();
setBasicAuth("jdoe", "changeit");
request.setMethod("GET");
final String redirectUri = "https://example.org/cb";
- AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
- new Pair<>("request_uri", requestUri)));
+ if (scope.contains("openid")) {
+ AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("request_uri", requestUri), new Pair<>("scope", scope.toString())));
+ } else {
+ AuthorizeFlowTest.setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("request_uri", requestUri)));
+ }
final FlowExecutionResult result = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
- if (jkt != null) {
+ if (jkt != null || nonce != null || essentialAcr != null) {
final String url = response.getRedirectedUrl();
assert url != null;
final String code = url.substring(url.indexOf("code=") + 5);
@@ -906,6 +944,17 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
final AuthorizeCodeClaimsSet claimsSet = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
assert claimsSet != null;
Assert.assertEquals(claimsSet.getDpopProofJwkThumbprint(), jkt);
+ Assert.assertEquals(claimsSet.getNonce(), nonce != null ? new Nonce(nonce) : null);
+ if (essentialAcr != null) {
+ final OIDCClaimsRequest claimsRequest = claimsSet.getClaimsRequest();
+ Assert.assertNotNull(claimsRequest);
+ assert claimsRequest != null;
+ final var acr = claimsRequest.getIDTokenClaimsRequest().get("acr");
+ Assert.assertNotNull(acr);
+ assert acr != null;
+ Assert.assertEquals(acr.getClaimRequirement(), ClaimRequirement.ESSENTIAL);
+ Assert.assertEquals(acr.getValuesAsListOfStrings(), List.of(essentialAcr));
+ }
} catch (ParseException | DataSealerException e) {
Assert.fail(e.getMessage());
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list