Skip to content

Commit 3804d7b

Browse files
committed
fix(oidc): invalid session cookie without encryption
1 parent 55c6021 commit 3804d7b

File tree

5 files changed

+88
-3
lines changed

5 files changed

+88
-3
lines changed

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTokenStateManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public Uni<String> createTokenState(RoutingContext routingContext, OidcTenantCon
8181
.append(CodeAuthenticationMechanism.COOKIE_DELIM)
8282
.append(tokens.getAccessTokenExpiresIn() != null ? tokens.getAccessTokenExpiresIn() : "")
8383
.append(CodeAuthenticationMechanism.COOKIE_DELIM)
84-
.append(tokens.getAccessTokenScope() != null ? tokens.getAccessTokenScope() : "");
84+
.append(tokens.getAccessTokenScope() != null ? encodeScopes(oidcConfig, tokens.getAccessTokenScope())
85+
: "");
8586

8687
// Encrypt access token and create a `q_session_at` cookie.
8788
CodeAuthenticationMechanism.createCookie(routingContext,
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.quarkus.it.keycloak;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.ws.rs.GET;
5+
import jakarta.ws.rs.Path;
6+
7+
import org.eclipse.microprofile.jwt.JsonWebToken;
8+
9+
import io.quarkus.oidc.TokenIntrospection;
10+
import io.quarkus.security.PermissionsAllowed;
11+
import io.quarkus.security.identity.SecurityIdentity;
12+
13+
@Path("/code-flow-token-introspection-no-encryption")
14+
@PermissionsAllowed(value = { "email", "openid", "profile" }, inclusive = true)
15+
public class CodeFlowTokenIntrospectionNoEncryptionResource {
16+
17+
@Inject
18+
SecurityIdentity identity;
19+
20+
@Inject
21+
TokenIntrospection tokenIntrospection;
22+
23+
@GET
24+
public String access() {
25+
if (identity.getPrincipal() instanceof JsonWebToken) {
26+
return identity.getPrincipal().getName();
27+
} else {
28+
return identity.getPrincipal().getName() + ":" + tokenIntrospection.getUsername();
29+
}
30+
}
31+
}

integration-tests/oidc-wiremock/src/main/java/io/quarkus/it/keycloak/CustomSecurityIdentityAugmentor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRe
4545
(routingContext.normalizedPath().endsWith("code-flow-user-info-only")
4646
|| routingContext.normalizedPath().endsWith("code-flow-user-info-dynamic-github")
4747
|| routingContext.normalizedPath().endsWith("code-flow-token-introspection")
48+
|| routingContext.normalizedPath().endsWith("code-flow-token-introspection-no-encryption")
4849
|| routingContext.normalizedPath().endsWith("code-flow-user-info-github-cached-in-idtoken")
4950
|| routingContext.normalizedPath().endsWith("code-flow-user-info-github-cache-disabled"))) {
5051
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder(identity);

integration-tests/oidc-wiremock/src/main/resources/application.properties

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ quarkus.oidc.code-flow-token-introspection.client-id=quarkus-web-app
173173
quarkus.oidc.code-flow-token-introspection.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
174174
quarkus.oidc.code-flow-token-introspection.code-grant.headers.X-Custom=XTokenIntrospection
175175

176+
quarkus.oidc.code-flow-token-introspection-no-encryption.provider=github
177+
quarkus.oidc.code-flow-token-introspection-no-encryption.token.verify-access-token-with-user-info=false
178+
quarkus.oidc.code-flow-token-introspection-no-encryption.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
179+
quarkus.oidc.code-flow-token-introspection-no-encryption.authorization-path=/
180+
quarkus.oidc.code-flow-token-introspection-no-encryption.user-info-path=protocol/openid-connect/userinfo
181+
quarkus.oidc.code-flow-token-introspection-no-encryption.introspection-path=protocol/openid-connect/token/introspect
182+
quarkus.oidc.code-flow-token-introspection-no-encryption.token.refresh-token-time-skew=298
183+
quarkus.oidc.code-flow-token-introspection-no-encryption.roles.source=accesstoken
184+
quarkus.oidc.code-flow-token-introspection-no-encryption.client-id=quarkus-web-app
185+
quarkus.oidc.code-flow-token-introspection-no-encryption.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
186+
quarkus.oidc.code-flow-token-introspection-no-encryption.code-grant.headers.X-Custom=XTokenIntrospection
187+
quarkus.oidc.code-flow-token-introspection-no-encryption.token-state-manager.split-tokens=true
188+
quarkus.oidc.code-flow-token-introspection-no-encryption.token-state-manager.encryption-required=false
189+
176190
quarkus.oidc.code-flow-token-introspection-expires-in.auth-server-url=${keycloak.url:replaced-by-test-resource}/realms/quarkus/
177191
quarkus.oidc.code-flow-token-introspection-expires-in.application-type=web-app
178192
quarkus.oidc.code-flow-token-introspection-expires-in.authentication.user-info-required=false

integration-tests/oidc-wiremock/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,44 @@ public void testCodeFlowTokenIntrospectionActiveRefresh() throws Exception {
545545
clearCache();
546546
}
547547

548+
@Test
549+
public void testCodeFlowTokenIntrospectionActiveRefresh_noEncryption() throws Exception {
550+
// exactly as testCodeFlowTokenIntrospectionActiveRefresh but with
551+
// quarkus.oidc.token-state-manager.split-tokens=true
552+
// quarkus.oidc.token-state-manager.encryption-required=false
553+
// to assure that cookie is valid when there are multiple scopes
554+
555+
// This stub does not return an access token expires_in property
556+
defineCodeFlowTokenIntrospectionStub();
557+
try (final WebClient webClient = createWebClient()) {
558+
webClient.getOptions().setRedirectEnabled(true);
559+
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow-token-introspection-no-encryption");
560+
561+
HtmlForm form = page.getFormByName("form");
562+
form.getInputByName("username").type("alice");
563+
form.getInputByName("password").type("alice");
564+
565+
TextPage textPage = form.getInputByValue("login").click();
566+
567+
assertEquals("alice:alice", textPage.getContent());
568+
569+
textPage = webClient.getPage("http://localhost:8081/code-flow-token-introspection-no-encryption");
570+
assertEquals("alice:alice", textPage.getContent());
571+
572+
// Refresh
573+
// The internal ID token lifespan is 5 mins
574+
// Configured refresh token skew is 298 secs = 5 mins - 2 secs
575+
// Therefore, after waiting for 3 secs, an active refresh is happening
576+
Thread.sleep(3000);
577+
textPage = webClient.getPage("http://localhost:8081/code-flow-token-introspection-no-encryption");
578+
assertEquals("admin:admin", textPage.getContent());
579+
580+
webClient.getCookieManager().clearCookies();
581+
}
582+
583+
clearCache();
584+
}
585+
548586
@Test
549587
public void testCodeFlowTokenIntrospectionExpiresInRefresh() throws Exception {
550588
// This stub does return an access token expires_in property
@@ -844,7 +882,7 @@ private void defineCodeFlowTokenIntrospectionStub() {
844882
.withHeader("Content-Type", "application/json")
845883
.withBody("{\n" +
846884
" \"access_token\": \"alice\","
847-
+ " \"scope\": \"email\","
885+
+ " \"scope\": \"openid profile email\","
848886
+ " \"refresh_token\": \"refresh5678\""
849887
+ "}")));
850888

@@ -855,7 +893,7 @@ private void defineCodeFlowTokenIntrospectionStub() {
855893
.withHeader("Content-Type", "application/json")
856894
.withBody("{\n" +
857895
" \"access_token\": \"admin\","
858-
+ " \"scope\": \"email\""
896+
+ " \"scope\": \"openid profile email\""
859897
+ "}")));
860898
}
861899

0 commit comments

Comments
 (0)