Skip to content

Commit

Permalink
Make sure the code flow access token is propagated during the authent…
Browse files Browse the repository at this point in the history
…ication
  • Loading branch information
sberyozkin committed Jan 29, 2024
1 parent c0faacf commit 1346815
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 13 deletions.
5 changes: 5 additions & 0 deletions extensions/oidc-token-propagation-reactive/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,9 +17,6 @@ public class FrontendResource {
@Inject
JsonWebToken jwt;

@Inject
CurrentIdentityAssociation identityAssociation;

@GET
@Path("token-propagation")
@RolesAllowed("admin")
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -42,7 +45,12 @@ protected Uni<SecurityIdentity> 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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=";
Expand Down Expand Up @@ -364,24 +369,33 @@ private Set<String> getUserRoles() {
}

public static String getAccessToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups);
return generateJwtToken(userName, groups, TOKEN_SUBJECT, BEARER_TOKEN_TYPE);
}

public static String getIdToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups);
return generateJwtToken(userName, groups, TOKEN_SUBJECT, ID_TOKEN_TYPE);
}

public static String generateJwtToken(String userName, Set<String> groups) {
return generateJwtToken(userName, groups, "123456");
return generateJwtToken(userName, groups, TOKEN_SUBJECT);
}

public static String generateJwtToken(String userName, Set<String> groups, String sub) {
return Jwt.preferredUserName(userName)
return generateJwtToken(userName, groups, sub, null);
}

public static String generateJwtToken(String userName, Set<String> 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");
Expand Down

0 comments on commit 1346815

Please sign in to comment.