Skip to content

Commit

Permalink
Merge pull request #43585 from sberyozkin/oidc_self_signed_idtoken_check
Browse files Browse the repository at this point in the history
Check self-signed ID token when access token is verified
  • Loading branch information
sberyozkin authored Sep 30, 2024
2 parents 40a35cb + d8ff7e4 commit ffe6bae
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,8 @@ public Uni<SecurityIdentity> apply(UserInfo userInfo, Throwable t) {
return validateTokenWithUserInfoAndCreateIdentity(requestData, request, resolvedContext, null);
}
} else {
final Uni<TokenVerificationResult> primaryTokenUni;
if (isInternalIdToken(request)) {
if (requestData.get(NEW_AUTHENTICATION) == Boolean.TRUE) {
// No need to verify it in this case as 'CodeAuthenticationMechanism' has just created it
primaryTokenUni = Uni.createFrom()
.item(new TokenVerificationResult(OidcUtils.decodeJwtContent(request.getToken().getToken()), null));
} else {
primaryTokenUni = verifySelfSignedTokenUni(resolvedContext, request.getToken().getToken());
}
} else {
primaryTokenUni = verifyTokenUni(requestData, resolvedContext, request.getToken(),
isIdToken(request), null);
}
final Uni<TokenVerificationResult> primaryTokenUni = verifyPrimaryTokenUni(requestData, request, resolvedContext,
null);

return getUserInfoAndCreateIdentity(primaryTokenUni, requestData, request, resolvedContext);
}
Expand All @@ -174,11 +163,10 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult codeAccessToken, Thro
requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessToken);
}

Uni<TokenVerificationResult> tokenUni = verifyTokenUni(requestData, resolvedContext,
request.getToken(),
false, userInfo);
Uni<TokenVerificationResult> primaryTokenUni = verifyPrimaryTokenUni(requestData, request,
resolvedContext, userInfo);

return tokenUni.onItemOrFailure()
return primaryTokenUni.onItemOrFailure()
.transformToUni(
new BiFunction<TokenVerificationResult, Throwable, Uni<? extends SecurityIdentity>>() {
@Override
Expand All @@ -196,6 +184,22 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult result, Throwable t)
});
}

private Uni<TokenVerificationResult> verifyPrimaryTokenUni(Map<String, Object> requestData,
TokenAuthenticationRequest request, TenantConfigContext resolvedContext, UserInfo userInfo) {
if (isInternalIdToken(request)) {
if (requestData.get(NEW_AUTHENTICATION) == Boolean.TRUE) {
// No need to verify it in this case as 'CodeAuthenticationMechanism' has just created it
return Uni.createFrom()
.item(new TokenVerificationResult(OidcUtils.decodeJwtContent(request.getToken().getToken()), null));
} else {
return verifySelfSignedTokenUni(resolvedContext, request.getToken().getToken());
}
} else {
return verifyTokenUni(requestData, resolvedContext, request.getToken(),
isIdToken(request), userInfo);
}
}

private Uni<SecurityIdentity> getUserInfoAndCreateIdentity(Uni<TokenVerificationResult> tokenUni,
Map<String, Object> requestData,
TokenAuthenticationRequest request,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.it.keycloak;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.eclipse.microprofile.jwt.JsonWebToken;

import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.security.Authenticated;

@Path("/code-flow-opaque-access-token")
@Authenticated
public class CodeFlowOpaqueAccessTokenResource {

@Inject
JsonWebToken jwtAccessToken;

@Inject
AccessTokenCredential accessTokenCredential;

@GET
public String getAccessTokenCredential() {
return accessTokenCredential.getToken();
}

@GET
@Path("/jwt-access-token")
public String getJwtAccessToken() {
return jwtAccessToken.getRawToken();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ quarkus.oidc.code-flow-verify-id-and-access-tokens.credentials.secret=secret
quarkus.oidc.code-flow-verify-id-and-access-tokens.application-type=web-app
quarkus.oidc.code-flow-verify-id-and-access-tokens.token.audience=any

quarkus.oidc.code-flow-opaque-access-token.provider=github
quarkus.oidc.code-flow-opaque-access-token.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.code-flow-opaque-access-token.authorization-path=/
quarkus.oidc.code-flow-opaque-access-token.token-path=${keycloak.url}/realms/quarkus/opaque-access-token
quarkus.oidc.code-flow-opaque-access-token.user-info-path=protocol/openid-connect/userinfo
quarkus.oidc.code-flow-opaque-access-token.client-id=quarkus-web-app
quarkus.oidc.code-flow-opaque-access-token.credentials.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow
quarkus.oidc.code-flow-opaque-access-token.tenant-paths=/code-flow-opaque-access-token/*

quarkus.oidc.code-flow-encrypted-id-token-jwk.auth-server-url=${keycloak.url}/realms/quarkus/
quarkus.oidc.code-flow-encrypted-id-token-jwk.client-id=quarkus-web-app
quarkus.oidc.code-flow-encrypted-id-token-jwk.credentials.secret=secret
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
Expand All @@ -37,6 +38,7 @@
import org.awaitility.core.ThrowingRunnable;
import org.eclipse.microprofile.jwt.Claims;
import org.hamcrest.Matchers;
import org.htmlunit.FailingHttpStatusCodeException;
import org.htmlunit.SilentCssErrorHandler;
import org.htmlunit.TextPage;
import org.htmlunit.WebClient;
Expand Down Expand Up @@ -109,6 +111,33 @@ public void testCodeFlow() throws IOException {
clearCache();
}

@Test
public void testCodeFlowOpaqueAccessToken() throws IOException {
defineCodeFlowOpaqueAccessTokenStub();
try (final WebClient webClient = createWebClient()) {
webClient.getOptions().setRedirectEnabled(true);
HtmlPage page = webClient.getPage("http://localhost:8081/code-flow-opaque-access-token");

HtmlForm form = page.getFormByName("form");
form.getInputByName("username").type("alice");
form.getInputByName("password").type("alice");

TextPage textPage = form.getInputByValue("login").click();

assertEquals("alice", textPage.getContent());

try {
webClient.getPage("http://localhost:8081/code-flow-opaque-access-token/jwt-access-token");
fail("500 status error is expected");
} catch (FailingHttpStatusCodeException ex) {
assertEquals(500, ex.getStatusCode());
}

webClient.getCookieManager().clearCookies();
}
clearCache();
}

@Test
public void testCodeFlowVerifyIdAndAccessToken() throws IOException {
defineCodeFlowLogoutStub();
Expand Down Expand Up @@ -676,6 +705,16 @@ private void defineCodeFlowUserInfoCachedInIdTokenStub(String expiredRefreshToke

}

private void defineCodeFlowOpaqueAccessTokenStub() {
wireMockServer
.stubFor(WireMock.post(urlPathMatching("/auth/realms/quarkus/opaque-access-token"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withBody("{\n" +
" \"access_token\": \"alice\","
+ "\"expires_in\": 299}")));
}

private String generateAlreadyExpiredRefreshToken() {
return Jwt.claims().expiresIn(0).signWithSecret("0123456789ABCDEF0123456789ABCDEF");
}
Expand Down

0 comments on commit ffe6bae

Please sign in to comment.