Skip to content

Commit

Permalink
Merge pull request #34610 from sberyozkin/dont_refresh_if_logout_requ…
Browse files Browse the repository at this point in the history
…ested

Do not refresh OIDC session if the user is requesting logout
  • Loading branch information
sberyozkin authored Jul 7, 2023
2 parents a9de803 + 2a5d703 commit 04211eb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public TokenVerificationResult removeTokenVerification(String token) {
return entry == null ? null : entry.result;
}

public boolean containsTokenVerification(String token) {
return cacheMap.containsKey(token);
}

public void clearCache() {
cacheMap.clear();
size.set(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,30 +275,7 @@ public Uni<? extends SecurityIdentity> apply(AuthorizationCodeTokens session) {
return authenticate(identityProviderManager, context,
new IdTokenCredential(currentIdToken,
isInternalIdToken(currentIdToken, configContext)))
.call(new Function<SecurityIdentity, Uni<?>>() {
@Override
public Uni<Void> apply(SecurityIdentity identity) {
if (isLogout(context, configContext)) {
LOG.debug("Performing an RP initiated logout");
fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);
return buildLogoutRedirectUriUni(context, configContext,
session.getIdToken());
}
if (isBackChannelLogoutPendingAndValid(configContext, identity)
|| isFrontChannelLogoutValid(context, configContext,
identity)) {
return removeSessionCookie(context, configContext.oidcConfig)
.map(new Function<Void, Void>() {
@Override
public Void apply(Void t) {
throw new LogoutException();
}
});

}
return VOID_UNI;
}
}).onFailure()
.call(new LogoutCall(context, configContext, session.getIdToken())).onFailure()
.recoverWithUni(new Function<Throwable, Uni<? extends SecurityIdentity>>() {
@Override
public Uni<? extends SecurityIdentity> apply(Throwable t) {
Expand Down Expand Up @@ -344,26 +321,35 @@ public Uni<? extends SecurityIdentity> apply(Throwable t) {
session.getRefreshToken(),
context,
identityProviderManager, false, null);
} else if (session.getRefreshToken() != null) {
// Token has nearly expired, try to refresh
LOG.debug("Token auto-refresh is starting");
return refreshSecurityIdentity(configContext,
currentIdToken,
session.getRefreshToken(),
context,
identityProviderManager, true,
((TokenAutoRefreshException) t).getSecurityIdentity());
} else {
LOG.debug(
"Token auto-refresh is required but is not possible because the refresh token is null");
// Auto-refreshing is not possible, just continue with the current security identity
// Token auto-refresh, security identity is still valid
SecurityIdentity currentIdentity = ((TokenAutoRefreshException) t)
.getSecurityIdentity();
if (currentIdentity != null) {
return Uni.createFrom().item(currentIdentity);
if (isLogout(context, configContext, currentIdentity)) {
// No need to refresh the token since the user is requesting a logout
return Uni.createFrom().item(currentIdentity).call(
new LogoutCall(context, configContext, session.getIdToken()));
}

if (session.getRefreshToken() != null) {
// Token has nearly expired, try to refresh
LOG.debug("Token auto-refresh is starting");
return refreshSecurityIdentity(configContext,
currentIdToken,
session.getRefreshToken(),
context,
identityProviderManager, true,
currentIdentity);
} else {
return Uni.createFrom()
.failure(new AuthenticationFailedException(t.getCause()));
LOG.debug(
"Token auto-refresh is required but is not possible because the refresh token is null");
// Auto-refreshing is not possible, just continue with the current security identity
if (currentIdentity != null) {
return Uni.createFrom().item(currentIdentity);
} else {
return Uni.createFrom()
.failure(new AuthenticationFailedException(t.getCause()));
}
}
}
}
Expand All @@ -388,8 +374,31 @@ private static String decryptIdTokenIfEncryptedByProvider(TenantConfigContext re
return token;
}

private boolean isBackChannelLogoutPendingAndValid(TenantConfigContext configContext, SecurityIdentity identity) {
private boolean isLogout(RoutingContext context, TenantConfigContext configContext, SecurityIdentity identity) {
return isRpInitiatedLogout(context, configContext) || isBackChannelLogoutPending(configContext, identity)
|| isFrontChannelLogoutValid(context, configContext, identity);
}

private boolean isBackChannelLogoutPending(TenantConfigContext configContext, SecurityIdentity identity) {
if (configContext.oidcConfig.logout.backchannel.path.isEmpty()) {
return false;
}
BackChannelLogoutTokenCache tokens = resolver.getBackChannelLogoutTokens()
.get(configContext.oidcConfig.getTenantId().get());
if (tokens != null) {
JsonObject idTokenJson = OidcUtils.decodeJwtContent(((JsonWebToken) (identity.getPrincipal())).getRawToken());

String logoutTokenKeyValue = idTokenJson.getString(configContext.oidcConfig.logout.backchannel.getLogoutTokenKey());

return tokens.containsTokenVerification(logoutTokenKeyValue);
}
return false;
}

private boolean isBackChannelLogoutPendingAndValid(TenantConfigContext configContext, SecurityIdentity identity) {
if (configContext.oidcConfig.logout.backchannel.path.isEmpty()) {
return false;
}
BackChannelLogoutTokenCache tokens = resolver.getBackChannelLogoutTokens()
.get(configContext.oidcConfig.getTenantId().get());
if (tokens != null) {
Expand Down Expand Up @@ -1014,7 +1023,7 @@ private String buildUri(RoutingContext context, boolean forceHttps, String autho
.toString();
}

private boolean isLogout(RoutingContext context, TenantConfigContext configContext) {
private boolean isRpInitiatedLogout(RoutingContext context, TenantConfigContext configContext) {
return isEqualToRequestPath(configContext.oidcConfig.logout.path, context, configContext);
}

Expand Down Expand Up @@ -1205,4 +1214,38 @@ static String getCookieSuffix(OidcTenantConfig oidcConfig) {
? (tenantIdSuffix + UNDERSCORE + oidcConfig.authentication.cookieSuffix.get())
: tenantIdSuffix;
}

private class LogoutCall implements Function<SecurityIdentity, Uni<?>> {
RoutingContext context;
TenantConfigContext configContext;
String idToken;

LogoutCall(RoutingContext context, TenantConfigContext configContext, String idToken) {
this.context = context;
this.configContext = configContext;
this.idToken = idToken;
}

@Override
public Uni<Void> apply(SecurityIdentity identity) {
if (isRpInitiatedLogout(context, configContext)) {
LOG.debug("Performing an RP initiated logout");
fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);
return buildLogoutRedirectUriUni(context, configContext, idToken);
}
if (isBackChannelLogoutPendingAndValid(configContext, identity)
|| isFrontChannelLogoutValid(context, configContext,
identity)) {
return removeSessionCookie(context, configContext.oidcConfig)
.map(new Function<Void, Void>() {
@Override
public Void apply(Void t) {
throw new LogoutException();
}
});

}
return VOID_UNI;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ quarkus.oidc.code-flow.logout.extra-params.client_id=${quarkus.oidc.code-flow.cl
quarkus.oidc.code-flow.credentials.secret=secret
quarkus.oidc.code-flow.application-type=web-app
quarkus.oidc.code-flow.token.audience=https://server.example.com
quarkus.oidc.code-flow.token.refresh-expired=true
quarkus.oidc.code-flow.token.refresh-token-time-skew=5M

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
Expand Down Expand Up @@ -77,7 +76,7 @@ public void testCodeFlow() throws IOException {

assertEquals("alice, cache size: 0", page.getBody().asNormalizedText());
assertNotNull(getSessionCookie(webClient, "code-flow"));

// Logout
page = webClient.getPage("http://localhost:8081/code-flow/logout");
assertEquals("Welcome, clientId: quarkus-web-app", page.getBody().asNormalizedText());
assertNull(getSessionCookie(webClient, "code-flow"));
Expand Down

0 comments on commit 04211eb

Please sign in to comment.