This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch 3.6.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 40f2f7b67685311859312c50e402c10be4abe641 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Wed Jun 17 16:35:14 2026 +0100 Require c_hash for the hybrid case (#3230) (cherry picked from commit d9d778e889de2e99c900a73415a1a00bdc284389) (cherry picked from commit 3fc1171a481a3f99eb516fe8cfb10805039ec984) --- .../security/oidc/idp/IdTokenResponseFilter.java | 3 + .../cxf/rs/security/oidc/rp/IdTokenReader.java | 14 ++++- .../cxf/rs/security/oidc/rp/IdTokenReaderTest.java | 70 ++++++++++++++++++++++ .../systest/jaxrs/security/oidc/OIDCFlowTest.java | 2 + 4 files changed, 88 insertions(+), 1 deletion(-) diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java index 674371ea26e..59bba1a6d73 100644 --- a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/IdTokenResponseFilter.java @@ -56,6 +56,9 @@ public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements if (ct.getApprovedScope() == null || !ct.getApprovedScope().contains(OidcUtils.OPENID_SCOPE)) { return; } + if (st.getResponseType() != null) { + ct.getParameters().put(OAuthConstants.RESPONSE_TYPE, st.getResponseType()); + } String idToken = getProcessedIdToken(st); if (idToken != null) { ct.getParameters().put(OidcUtils.ID_TOKEN, idToken); diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReader.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReader.java index a28947d6627..38e0488e838 100644 --- a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReader.java +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReader.java @@ -21,6 +21,7 @@ package org.apache.cxf.rs.security.oidc.rp; import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.client.Consumer; import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; +import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; import org.apache.cxf.rs.security.oidc.common.IdToken; import org.apache.cxf.rs.security.oidc.utils.OidcUtils; @@ -44,7 +45,11 @@ public class IdTokenReader extends OidcClaimsValidator { String idJwtToken = at.getParameters().get(OidcUtils.ID_TOKEN); JwtToken jwt = getIdJwtToken(idJwtToken, client); OidcUtils.validateAccessTokenHash(at, jwt, requireAtHash); - OidcUtils.validateCodeHash(code, jwt, requireCodeHash); + if (code != null) { + // The spec requires c_hash to be present in the id_token for hybrid flows, + // but we allow it to be optional for token endpoint id_tokens + OidcUtils.validateCodeHash(code, jwt, requireCodeHash || isHybridFlow(at)); + } return jwt; } public JwtToken getIdJwtToken(ClientAccessToken at, Consumer client) { @@ -55,6 +60,13 @@ public class IdTokenReader extends OidcClaimsValidator { validateJwtClaims(jwt.getClaims(), client.getClientId(), true); return jwt; } + + private boolean isHybridFlow(ClientAccessToken at) { + String responseType = at.getParameters().get(OAuthConstants.RESPONSE_TYPE); + return OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE.equals(responseType) + || OidcUtils.CODE_ID_TOKEN_AT_RESPONSE_TYPE.equals(responseType); + } + private IdToken getIdTokenFromJwt(JwtToken jwt) { return new IdToken(jwt.getClaims().asMap()); } diff --git a/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReaderTest.java b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReaderTest.java new file mode 100644 index 00000000000..7f8bb97f2e8 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/IdTokenReaderTest.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cxf.rs.security.oidc.rp; + +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.cxf.rs.security.oauth2.client.Consumer; +import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; +import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException; +import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; +import org.apache.cxf.rs.security.oidc.utils.OidcUtils; + +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +public class IdTokenReaderTest { + + @Test + public void testCodeHashIsOptionalByDefaultForTokenEndpointIdToken() { + IdTokenReader idTokenReader = new StubIdTokenReader(new JwtToken(new JwtClaims())); + idTokenReader.setRequireAccessTokenHash(false); + + ClientAccessToken accessToken = new ClientAccessToken("Bearer", "access-token"); + accessToken.getParameters().put(OidcUtils.ID_TOKEN, "id-token"); + + assertNotNull(idTokenReader.getIdJwtToken(accessToken, "auth-code", new Consumer("client-id"))); + } + + @Test(expected = OAuthServiceException.class) + public void testCodeHashIsRequiredByDefaultForHybridTokenEndpointIdToken() { + IdTokenReader idTokenReader = new StubIdTokenReader(new JwtToken(new JwtClaims())); + idTokenReader.setRequireAccessTokenHash(false); + + ClientAccessToken accessToken = new ClientAccessToken("Bearer", "access-token"); + accessToken.getParameters().put(OidcUtils.ID_TOKEN, "id-token"); + accessToken.getParameters().put(OAuthConstants.RESPONSE_TYPE, OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE); + + idTokenReader.getIdJwtToken(accessToken, "auth-code", new Consumer("client-id")); + } + + private static final class StubIdTokenReader extends IdTokenReader { + private final JwtToken jwt; + + private StubIdTokenReader(JwtToken jwt) { + this.jwt = jwt; + } + + @Override + public JwtToken getIdJwtToken(String idJwtToken, Consumer client) { + return jwt; + } + } +} diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCFlowTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCFlowTest.java index 19059820202..c621d065b69 100644 --- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCFlowTest.java +++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCFlowTest.java @@ -656,6 +656,8 @@ public class OIDCFlowTest extends AbstractBusClientServerTestBase { OAuth2TestUtils.getAccessTokenWithAuthorizationCode(client, code); assertNotNull(accessToken.getTokenKey()); assertTrue(accessToken.getApprovedScope().contains("openid")); + assertEquals(OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE, + accessToken.getParameters().get(OAuthConstants.RESPONSE_TYPE)); // Check id_token from the token endpoint idToken = accessToken.getParameters().get("id_token");
