diff --git a/extensions/oidc-token-propagation-reactive/deployment/pom.xml b/extensions/oidc-token-propagation-reactive/deployment/pom.xml index b2da0cf50cd25..1c3757368a4f0 100644 --- a/extensions/oidc-token-propagation-reactive/deployment/pom.xml +++ b/extensions/oidc-token-propagation-reactive/deployment/pom.xml @@ -56,6 +56,11 @@ rest-assured test + + net.sourceforge.htmlunit + htmlunit + test + diff --git a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/FrontendResource.java b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/FrontendResource.java index a5a3d0ee9fe97..960c894cad4f0 100644 --- a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/FrontendResource.java +++ b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/FrontendResource.java @@ -8,8 +8,6 @@ import org.eclipse.microprofile.jwt.JsonWebToken; import org.eclipse.microprofile.rest.client.inject.RestClient; -import io.quarkus.security.identity.CurrentIdentityAssociation; - @Path("/frontend") public class FrontendResource { @Inject @@ -19,9 +17,6 @@ public class FrontendResource { @Inject JsonWebToken jwt; - @Inject - CurrentIdentityAssociation identityAssociation; - @GET @Path("token-propagation") @RolesAllowed("admin") @@ -31,7 +26,7 @@ public String userNameTokenPropagation() { @GET @Path("token-propagation-with-augmentor") - @RolesAllowed("tester") // tester role is granted by SecurityIdentityAugmentor + @RolesAllowed("Bearertester") // Bearertester role is granted by SecurityIdentityAugmentor public String userNameTokenPropagationWithSecIdentityAugmentor() { return getResponseWithExchangedUsername(); } diff --git a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/OidcTokenPropagationWithSecurityIdentityAugmentorTest.java b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/OidcTokenPropagationWithSecurityIdentityAugmentorTest.java index e918f9f04d6af..79d4d1f89b9c5 100644 --- a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/OidcTokenPropagationWithSecurityIdentityAugmentorTest.java +++ b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/OidcTokenPropagationWithSecurityIdentityAugmentorTest.java @@ -2,13 +2,21 @@ import static io.quarkus.oidc.token.propagation.reactive.RolesSecurityIdentityAugmentor.SUPPORTED_USER; import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.IOException; import java.util.Set; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import com.gargoylesoftware.htmlunit.SilentCssErrorHandler; +import com.gargoylesoftware.htmlunit.TextPage; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + import io.quarkus.test.QuarkusUnitTest; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.oidc.server.OidcWiremockTestResource; @@ -51,4 +59,25 @@ public String getBearerAccessToken() { return OidcWiremockTestResource.getAccessToken(SUPPORTED_USER, Set.of("admin")); } + @Test + public void testGetUserNameWithTokenPropagationWithCodeFlow() throws IOException, InterruptedException { + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8081/frontend/token-propagation-with-augmentor"); + + HtmlForm form = page.getFormByName("form"); + form.getInputByName("username").type("alice"); + form.getInputByName("password").type("alice"); + + TextPage textPage = form.getInputByValue("login").click(); + + assertEquals("Token issued to alice has been exchanged, new user name: bob", textPage.getContent()); + webClient.getCookieManager().clearCookies(); + } + } + + private WebClient createWebClient() { + WebClient webClient = new WebClient(); + webClient.setCssErrorHandler(new SilentCssErrorHandler()); + return webClient; + } } diff --git a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/RolesResource.java b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/RolesResource.java index 80d7167434c0c..6ea97cbe2a7c1 100644 --- a/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/RolesResource.java +++ b/extensions/oidc-token-propagation-reactive/deployment/src/test/java/io/quarkus/oidc/token/propagation/reactive/RolesResource.java @@ -19,7 +19,8 @@ public class RolesResource { @GET public String get() { if ("bob".equals(jwt.getName())) { - return "tester"; + String tokenType = jwt.getClaim("typ"); + return tokenType + "tester"; } throw new ForbiddenException("Only user 'bob' is allowed to request roles"); } diff --git a/extensions/oidc-token-propagation-reactive/deployment/src/test/resources/application.properties b/extensions/oidc-token-propagation-reactive/deployment/src/test/resources/application.properties index 04051ed31aa29..d0459eade8177 100644 --- a/extensions/oidc-token-propagation-reactive/deployment/src/test/resources/application.properties +++ b/extensions/oidc-token-propagation-reactive/deployment/src/test/resources/application.properties @@ -1,6 +1,8 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus quarkus.oidc.client-id=quarkus-app quarkus.oidc.credentials.secret=secret +quarkus.oidc.application-type=hybrid +quarkus.oidc.token.audience=any quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc-client.client-id=${quarkus.oidc.client-id} diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractOidcAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractOidcAuthenticationMechanism.java index d3cec873ebb47..000b9283f43b1 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractOidcAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/AbstractOidcAuthenticationMechanism.java @@ -1,5 +1,8 @@ package io.quarkus.oidc.runtime; +import io.quarkus.oidc.AccessTokenCredential; +import io.quarkus.oidc.IdTokenCredential; +import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.IdentityProviderManager; import io.quarkus.security.identity.SecurityIdentity; @@ -42,7 +45,12 @@ protected Uni authenticate(IdentityProviderManager identityPro // during authentication TokenCredential is not accessible via CDI, thus we put it to the duplicated context VertxContextSafetyToggle.validateContextIfExists(ERROR_MSG, ERROR_MSG); final var ctx = Vertx.currentContext(); - ctx.putLocal(TokenCredential.class.getName(), token); + // If the primary token is ID token then the code flow access token is available as + // a RoutingContext `access_token` property. + final var tokenCredential = (token instanceof IdTokenCredential) + ? new AccessTokenCredential(context.get(OidcConstants.ACCESS_TOKEN_VALUE)) + : token; + ctx.putLocal(TokenCredential.class.getName(), tokenCredential); return identityProviderManager .authenticate(HttpSecurityUtils.setRoutingContextAttribute(new TokenAuthenticationRequest(token), context)) .invoke(new Runnable() { diff --git a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java index fbdc8faa8d49d..9f76443d13692 100644 --- a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java +++ b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/server/OidcWiremockTestResource.java @@ -29,6 +29,7 @@ import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import io.smallrye.jwt.build.Jwt; +import io.smallrye.jwt.build.JwtClaimsBuilder; /** * Provides a mock OIDC server to tests. @@ -42,6 +43,10 @@ public class OidcWiremockTestResource implements QuarkusTestResourceLifecycleMan "https://server.example.com"); private static final String TOKEN_AUDIENCE = System.getProperty("quarkus.test.oidc.token.audience", "https://server.example.com"); + private static final String TOKEN_SUBJECT = "123456"; + private static final String BEARER_TOKEN_TYPE = "Bearer"; + private static final String ID_TOKEN_TYPE = "ID"; + private static final String TOKEN_USER_ROLES = System.getProperty("quarkus.test.oidc.token.user-roles", "user"); private static final String TOKEN_ADMIN_ROLES = System.getProperty("quarkus.test.oidc.token.admin-roles", "user,admin"); private static final String ENCODED_X5C = "MIIC+zCCAeOgAwIBAgIGAXx/E9rgMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMTEwMTQxMzUzMDBaFw0yMjEwMTQxMzUzMDBaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIicN95dXlQLBqEZUsqPhQopnjnPgGmW80NohEgNzZLqN0xW9cyJJrdJM5Z1lRrePHZGiJdd1XXn4fYasP6/cjRfMWal9X6dD5wlnOTP01/4beX5vctE6W4lZrI3kTFmZ+I69w7BaLsUPWgV1CYrtuldL3dr6xAnngK3hU+JraB2Ndw9llXib26HOZhCXKedCTYcUQieVJGPI0f8H1JNk88+PnwI+cUGgXHF56iTLv9QujI6AhIgextXdd21T0XiHgBkSlSSBeqIKAjfCW6zoXP+PJU+Lso24J3duG3mrbilqHZlmIWnLRaG0RmKOeedXIDHvAaMaVUOLaN9HBgNKo0CAwEAAaNTMFEwHQYDVR0OBBYEFMYGoBNHBTMvMT4DwClVHVVwn+5VMB8GA1UdIwQYMBaAFMYGoBNHBTMvMT4DwClVHVVwn+5VMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFulB0DKhykXGbGPIBPcj63ItLNilgl1i8i43my8fYdV6OBWLIhZ4InhpX1+XmYCNPNtu94Jy1csS00K2/Hhn4ByBd+6nd5DSr0W0VdVQyhLz3GW1nf0J3X2N+tD818O0KtKKPTq4p9reg/XtV+DNv7DeDAGzlfgRL4E4fQx6OYeuu35kGrPvAddIA70leJMELJRylCLfEcl2ne/Bht8cZVp7ZCxnfXnsc+7hCW84mhzGjJycA3E6TnZPD3pD+q9FoIAQMxMQqUCH71u9vTvz1Q5JdokuJJY2eTHSUKyHA9MwSFq8DFDICJFBoQuFyDlK5yxSUcQpR3mBwKdimj6oA0="; @@ -364,24 +369,33 @@ private Set getUserRoles() { } public static String getAccessToken(String userName, Set groups) { - return generateJwtToken(userName, groups); + return generateJwtToken(userName, groups, TOKEN_SUBJECT, BEARER_TOKEN_TYPE); } public static String getIdToken(String userName, Set groups) { - return generateJwtToken(userName, groups); + return generateJwtToken(userName, groups, TOKEN_SUBJECT, ID_TOKEN_TYPE); } public static String generateJwtToken(String userName, Set groups) { - return generateJwtToken(userName, groups, "123456"); + return generateJwtToken(userName, groups, TOKEN_SUBJECT); } public static String generateJwtToken(String userName, Set groups, String sub) { - return Jwt.preferredUserName(userName) + return generateJwtToken(userName, groups, sub, null); + } + + public static String generateJwtToken(String userName, Set groups, String sub, String type) { + JwtClaimsBuilder builder = Jwt.preferredUserName(userName) .groups(groups) .issuer(TOKEN_ISSUER) .audience(TOKEN_AUDIENCE) .claim("sid", "session-id") - .subject(sub) + .subject(sub); + if (type != null) { + builder.claim("typ", type); + } + + return builder .jws() .keyId("1") .sign("privateKey.jwk");