Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check the code flow access token after ID token #38843

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public Object get(String name) {
}

public boolean contains(String propertyName) {
return json.containsKey(propertyName) && !json.isNull(propertyName);
return json != null && json.containsKey(propertyName) && !json.isNull(propertyName);
}

public Set<String> getPropertyNames() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public class OidcIdentityProvider implements IdentityProvider<TokenAuthenticatio
static final String NEW_AUTHENTICATION = "new_authentication";

private static final Uni<TokenVerificationResult> NULL_CODE_ACCESS_TOKEN_UNI = Uni.createFrom().nullItem();
private static final String CODE_ACCESS_TOKEN_RESULT = "code_flow_access_token_result";

protected final DefaultTenantConfigResolver tenantResolver;
private final BlockingTaskRunner<Void> uniVoidOidcContext;
Expand Down Expand Up @@ -149,30 +148,7 @@ public Uni<SecurityIdentity> apply(UserInfo userInfo, Throwable t) {
isIdToken(request), null);
}

// Verify Code Flow access token first if it is available and has to be verified.
// It may be refreshed if it has or has nearly expired
Uni<TokenVerificationResult> codeAccessTokenUni = verifyCodeFlowAccessTokenUni(requestData, request,
resolvedContext,
null);
return codeAccessTokenUni.onItemOrFailure().transformToUni(
new BiFunction<TokenVerificationResult, Throwable, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TokenVerificationResult codeAccessTokenResult, Throwable t) {
if (t != null) {
return Uni.createFrom().failure(t instanceof AuthenticationFailedException ? t
: new AuthenticationFailedException(t));
}
if (codeAccessTokenResult != null) {
if (tokenAutoRefreshPrepared(codeAccessTokenResult, requestData,
resolvedContext.oidcConfig)) {
return Uni.createFrom().failure(new TokenAutoRefreshException(null));
}
requestData.put(CODE_ACCESS_TOKEN_RESULT, codeAccessTokenResult);
}
return getUserInfoAndCreateIdentity(primaryTokenUni, requestData, request, resolvedContext);
}
});

return getUserInfoAndCreateIdentity(primaryTokenUni, requestData, request, resolvedContext);
}
}

Expand All @@ -191,7 +167,7 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult codeAccessToken, Thro
}

if (codeAccessToken != null) {
requestData.put(CODE_ACCESS_TOKEN_RESULT, codeAccessToken);
requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessToken);
}

Uni<TokenVerificationResult> tokenUni = verifyTokenUni(requestData, resolvedContext,
Expand All @@ -217,7 +193,8 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult result, Throwable t)
}

private Uni<SecurityIdentity> getUserInfoAndCreateIdentity(Uni<TokenVerificationResult> tokenUni,
Map<String, Object> requestData, TokenAuthenticationRequest request,
Map<String, Object> requestData,
TokenAuthenticationRequest request,
TenantConfigContext resolvedContext) {

return tokenUni.onItemOrFailure()
Expand All @@ -227,21 +204,49 @@ public Uni<SecurityIdentity> apply(TokenVerificationResult result, Throwable t)
if (t != null) {
return Uni.createFrom().failure(new AuthenticationFailedException(t));
}
if (resolvedContext.oidcConfig.authentication.isUserInfoRequired().orElse(false)) {
return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure().transformToUni(
new BiFunction<UserInfo, Throwable, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(UserInfo userInfo, Throwable t) {
if (t != null) {
return Uni.createFrom().failure(new AuthenticationFailedException(t));

Uni<TokenVerificationResult> codeAccessTokenUni = verifyCodeFlowAccessTokenUni(requestData, request,
resolvedContext,
null);
return codeAccessTokenUni.onItemOrFailure().transformToUni(
new BiFunction<TokenVerificationResult, Throwable, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(TokenVerificationResult codeAccessTokenResult,
Throwable t) {
if (t != null) {
return Uni.createFrom().failure(t instanceof AuthenticationFailedException ? t
: new AuthenticationFailedException(t));
}
if (codeAccessTokenResult != null) {
if (tokenAutoRefreshPrepared(codeAccessTokenResult, requestData,
resolvedContext.oidcConfig)) {
return Uni.createFrom().failure(new TokenAutoRefreshException(null));
}
requestData.put(OidcUtils.CODE_ACCESS_TOKEN_RESULT, codeAccessTokenResult);
}

if (resolvedContext.oidcConfig.authentication.isUserInfoRequired().orElse(false)) {
return getUserInfoUni(requestData, request, resolvedContext).onItemOrFailure()
.transformToUni(
new BiFunction<UserInfo, Throwable, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<SecurityIdentity> apply(UserInfo userInfo,
Throwable t) {
if (t != null) {
return Uni.createFrom()
.failure(new AuthenticationFailedException(t));
}
return createSecurityIdentityWithOidcServer(result,
requestData, request,
resolvedContext, userInfo);
}
});
} else {
return createSecurityIdentityWithOidcServer(result, requestData, request,
resolvedContext, userInfo);
resolvedContext, null);
}
});
} else {
return createSecurityIdentityWithOidcServer(result, requestData, request, resolvedContext, null);
}
}
});

}
});
Expand Down Expand Up @@ -405,7 +410,8 @@ private static JsonObject getRolesJson(Map<String, Object> requestData, TenantCo
rolesJson = new JsonObject(userInfo.getJsonObject().toString());
} else if (tokenCred instanceof IdTokenCredential
&& resolvedContext.oidcConfig.roles.source.get() == Source.accesstoken) {
rolesJson = ((TokenVerificationResult) requestData.get(CODE_ACCESS_TOKEN_RESULT)).localVerificationResult;
rolesJson = ((TokenVerificationResult) requestData
.get(OidcUtils.CODE_ACCESS_TOKEN_RESULT)).localVerificationResult;
if (rolesJson == null) {
// JSON token representation may be null not only if it is an opaque access token
// but also if it is JWT and no JWK with a matching kid is available, asynchronous
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.jboss.logging.Logger;

import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.IdToken;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.oidc.RefreshToken;
import io.quarkus.oidc.TokenIntrospection;
Expand Down Expand Up @@ -78,18 +79,49 @@ UserInfo currentUserInfo() {
}

/**
* The producer method for the current UserInfo
* The producer method for the ID token TokenIntrospection only.
*
* @return the user info
* @return the ID token introspection
*/
@Produces
@RequestScoped
TokenIntrospection currentTokenIntrospection() {
@IdToken
TokenIntrospection idTokenIntrospection() {
return tokenIntrospectionFromIdentityAttribute();
}

/**
* The producer method for the current TokenIntrospection.
* <p/>
* This TokenIntrospection always represents the bearer access token introspection when the bearer access tokens
* are used.
* <p/>
* In case of the authorization code flow, it represents a code flow access token introspection
* if it has been enabled by setting the `quarkus.oidc.authentication.verify-access-token` property to `true`
* and an ID token introspection otherwise. Use the `@IdToken` qualifier if both ID and code flow access tokens
* must be introspected.
*
* @return the token introspection
*/
@Produces
@RequestScoped
TokenIntrospection tokenIntrospection() {
TokenVerificationResult codeFlowAccessTokenResult = (TokenVerificationResult) identity
.getAttribute(OidcUtils.CODE_ACCESS_TOKEN_RESULT);
if (codeFlowAccessTokenResult == null) {
return tokenIntrospectionFromIdentityAttribute();
} else {
return codeFlowAccessTokenResult.introspectionResult;
}
}

TokenIntrospection tokenIntrospectionFromIdentityAttribute() {
TokenIntrospection introspection = (TokenIntrospection) identity.getAttribute(OidcUtils.INTROSPECTION_ATTRIBUTE);
if (introspection == null) {
LOG.trace("TokenIntrospection is null");
introspection = new TokenIntrospection();
}
return introspection;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public final class OidcUtils {
public static final Integer MAX_COOKIE_VALUE_LENGTH = 4096;
public static final String POST_LOGOUT_COOKIE_NAME = "q_post_logout";
static final String UNDERSCORE = "_";
static final String CODE_ACCESS_TOKEN_RESULT = "code_flow_access_token_result";
static final String COMMA = ",";
static final Uni<Void> VOID_UNI = Uni.createFrom().voidItem();
static final BlockingTaskRunner<Void> deleteTokensRequestContext = new BlockingTaskRunner<Void>();
Expand Down Expand Up @@ -350,6 +351,10 @@ static QuarkusSecurityIdentity validateAndCreateIdentity(Map<String, Object> req
setSecurityIdentityConfigMetadata(builder, resolvedContext);
setBlockingApiAttribute(builder, vertxContext);
setTenantIdAttribute(builder, config);
TokenVerificationResult codeFlowAccessTokenResult = (TokenVerificationResult) requestData.get(CODE_ACCESS_TOKEN_RESULT);
if (codeFlowAccessTokenResult != null) {
builder.addAttribute(CODE_ACCESS_TOKEN_RESULT, codeFlowAccessTokenResult);
}
return builder.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.quarkus.oidc.TokenIntrospection;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;

Expand All @@ -14,8 +15,11 @@ public class CodeFlowTokenIntrospectionResource {
@Inject
SecurityIdentity identity;

@Inject
TokenIntrospection tokenIntrospection;

@GET
public String access() {
return identity.getPrincipal().getName();
return identity.getPrincipal().getName() + ":" + tokenIntrospection.getUsername();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,12 @@ public void testCodeFlowTokenIntrospection() throws Exception {

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

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

// refresh
Thread.sleep(3000);
textPage = webClient.getPage("http://localhost:8081/code-flow-token-introspection");
assertEquals("admin", textPage.getContent());
assertEquals("admin:admin", textPage.getContent());

webClient.getCookieManager().clearCookies();
}
Expand Down
Loading