Skip to content

Commit

Permalink
Honor OIDC logout requests when ID token has expired
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Sep 13, 2023
1 parent a45d2e5 commit 737d27f
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
*
*/
public class SecurityEvent {
public static final String SESSION_TOKENS_PROPERTY = "session-tokens";

public enum Type {
/**
* OIDC Login event which is reported after the first user authentication but also when the user's session
Expand All @@ -30,6 +32,12 @@ public enum Type {
*/
OIDC_LOGOUT_RP_INITIATED,

/**
* OIDC Logout event is reported when the current user has started an RP-initiated OIDC logout flow but the session has
* already expired.
*/
OIDC_LOGOUT_RP_INITIATED_SESSION_EXPIRED,

/**
* OIDC BackChannel Logout initiated event is reported when the BackChannel logout request to logout the current user
* has been received.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,14 @@ public Uni<? extends SecurityIdentity> apply(Throwable t) {
t.getCause())));
}
// Token has expired, try to refresh
if (isRpInitiatedLogout(context, configContext)) {
LOG.debug("Session has expired, performing an RP initiated logout");
fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED_SESSION_EXPIRED,
Map.of(SecurityEvent.SESSION_TOKENS_PROPERTY, session));
return Uni.createFrom().item((SecurityIdentity) null)
.call(() -> buildLogoutRedirectUriUni(context, configContext,
currentIdToken));
}
if (session.getRefreshToken() == null) {
LOG.debug(
"Token has expired, token refresh is not possible because the refresh token is null");
Expand Down Expand Up @@ -983,6 +991,12 @@ private void fireEvent(SecurityEvent.Type eventType, SecurityIdentity securityId
}
}

private void fireEvent(SecurityEvent.Type eventType, Map<String, Object> properties) {
if (resolver.isSecurityEventObserved()) {
resolver.getSecurityEvent().fire(new SecurityEvent(eventType, properties));
}
}

private String getRedirectPath(OidcTenantConfig oidcConfig, RoutingContext context) {
Authentication auth = oidcConfig.getAuthentication();
return auth.getRedirectPath().isPresent() ? auth.getRedirectPath().get() : context.request().path();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ quarkus.oidc.tenant-logout.application-type=web-app
quarkus.oidc.tenant-logout.authentication.cookie-path=/tenant-logout
quarkus.oidc.tenant-logout.logout.path=/tenant-logout/logout
quarkus.oidc.tenant-logout.logout.post-logout-path=/tenant-logout/post-logout
quarkus.oidc.tenant-logout.authentication.session-age-extension=2M
quarkus.oidc.tenant-logout.token.refresh-expired=true

quarkus.oidc.tenant-refresh.auth-server-url=${keycloak.url}/realms/logout-realm
quarkus.oidc.tenant-refresh.client-id=quarkus-app
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,22 @@ public void testRPInitiatedLogout() throws IOException {

page = webClient.getPage("http://localhost:8081/tenant-logout");
assertEquals("Sign in to logout-realm", page.getTitleText());

// login again
loginForm = page.getForms().get(0);
loginForm.getInputByName("username").setValueAttribute("alice");
loginForm.getInputByName("password").setValueAttribute("alice");
page = loginForm.getInputByName("login").click();
assertEquals("Tenant Logout, refreshed: false", page.asNormalizedText());

assertNotNull(getSessionCookie(webClient, "tenant-logout"));

await().atLeast(Duration.ofSeconds(11));

page = webClient.getPage("http://localhost:8081/tenant-logout/logout");
assertTrue(page.asNormalizedText().contains("You were logged out, please login again"));
assertNull(getSessionCookie(webClient, "tenant-logout"));

webClient.getCookieManager().clearCookies();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public Map<String, String> start() {
// revoke refresh tokens so that they can only be used once
logoutRealm.setRevokeRefreshToken(true);
logoutRealm.setRefreshTokenMaxReuse(0);
logoutRealm.setSsoSessionMaxLifespan(15);
logoutRealm.setSsoSessionMaxLifespan(10);
logoutRealm.setAccessTokenLifespan(5);
client.createRealm(logoutRealm);
realms.add(logoutRealm);
Expand Down

0 comments on commit 737d27f

Please sign in to comment.