Skip to content

Commit

Permalink
Merge pull request quarkusio#38843 from sberyozkin/avoid_accesstoken_…
Browse files Browse the repository at this point in the history
…verification_if_idtoken_failed

Check the code flow access token after ID token
  • Loading branch information
sberyozkin authored Feb 20, 2024
2 parents d972a37 + b8bdad1 commit 5515a66
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 47 deletions.
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

0 comments on commit 5515a66

Please sign in to comment.