Skip to content

Commit e2ec7c1

Browse files
authored
Merge pull request quarkusio#46864 from sberyozkin/oidc_logout_clear_site_data
Allow setting Clear-Site-Data on OIDC logout
2 parents 8e6ca6a + 9a623b4 commit e2ec7c1

File tree

10 files changed

+150
-20
lines changed

10 files changed

+150
-20
lines changed

docs/src/main/asciidoc/security-oidc-code-flow-authentication.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,15 @@ There are two main ways for the authentication information to expire: the tokens
11411141

11421142
Let's start with explicit logout operations.
11431143

1144+
[NOTE]
1145+
====
1146+
You can request setting https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data[Clear-Site-Data] directives for all of the logout operations with a `quarkus.oidc.logout.clear-site-data` configuration property. For example:
1147+
1148+
[source,properties]
1149+
----
1150+
quarkus.oidc.logout.clear-site-data=cache,cookies
1151+
----
1152+
====
11441153

11451154
[[user-initiated-logout]]
11461155
==== User-initiated logout

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Map;
99
import java.util.Optional;
1010
import java.util.OptionalInt;
11+
import java.util.Set;
1112

1213
import io.quarkus.oidc.common.runtime.OidcClientCommonConfig;
1314
import io.quarkus.oidc.common.runtime.OidcConstants;
@@ -482,6 +483,11 @@ public static class Logout implements io.quarkus.oidc.runtime.OidcTenantConfig.L
482483
*/
483484
public Map<String, String> extraParams;
484485

486+
/**
487+
* Clear-Site-Data header directives
488+
*/
489+
Optional<Set<ClearSiteData>> clearSiteData = Optional.of(Set.of());
490+
485491
/**
486492
* Back-Channel Logout configuration
487493
*/
@@ -545,6 +551,7 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Log
545551
postLogoutPath = mapping.postLogoutPath();
546552
postLogoutUriParam = mapping.postLogoutUriParam();
547553
extraParams = mapping.extraParams();
554+
clearSiteData = mapping.clearSiteData();
548555
backchannel.addConfigMappingValues(mapping.backchannel());
549556
frontchannel.addConfigMappingValues(mapping.frontchannel());
550557
}
@@ -578,6 +585,11 @@ public io.quarkus.oidc.runtime.OidcTenantConfig.Backchannel backchannel() {
578585
public io.quarkus.oidc.runtime.OidcTenantConfig.Frontchannel frontchannel() {
579586
return frontchannel;
580587
}
588+
589+
@Override
590+
public Optional<Set<ClearSiteData>> clearSiteData() {
591+
return clearSiteData;
592+
}
581593
}
582594

583595
/**

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,11 +1512,13 @@ public Uni<Void> apply(SecurityIdentity identity) {
15121512
if (isRpInitiatedLogout(context, configContext)) {
15131513
LOG.debug("Performing an RP initiated logout");
15141514
fireEvent(SecurityEvent.Type.OIDC_LOGOUT_RP_INITIATED, identity);
1515+
OidcUtils.setClearSiteData(context, configContext.oidcConfig());
15151516
return buildLogoutRedirectUriUni(context, configContext, idToken);
15161517
}
15171518
if (isBackChannelLogoutPendingAndValid(configContext, identity)
15181519
|| isFrontChannelLogoutValid(context, configContext,
15191520
identity)) {
1521+
OidcUtils.setClearSiteData(context, configContext.oidcConfig());
15201522
return removeSessionCookie(context, configContext.oidcConfig())
15211523
.map(new Function<Void, Void>() {
15221524
@Override
@@ -1528,5 +1530,6 @@ public Void apply(Void t) {
15281530
}
15291531
return VOID_UNI;
15301532
}
1533+
15311534
}
15321535
}

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcSessionImpl.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public Uni<Void> logout() {
3939
return oidcConfigUni.onItem().transformToUni(new Function<OidcTenantConfig, Uni<? extends Void>>() {
4040
@Override
4141
public Uni<Void> apply(OidcTenantConfig oidcConfig) {
42+
OidcUtils.setClearSiteData(routingContext, oidcConfig);
4243
return OidcUtils.removeSessionCookie(routingContext, oidcConfig,
4344
resolver.getTokenStateManager());
4445
}

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Map;
77
import java.util.Optional;
88
import java.util.OptionalInt;
9+
import java.util.Set;
910

1011
import io.quarkus.oidc.JavaScriptRequestChecker;
1112
import io.quarkus.oidc.TenantConfigResolver;
@@ -291,6 +292,53 @@ interface Logout {
291292
* Front-Channel Logout configuration
292293
*/
293294
Frontchannel frontchannel();
295+
296+
enum ClearSiteData {
297+
/**
298+
* Clear cache
299+
*/
300+
CACHE("cache"),
301+
302+
/**
303+
* Clear client hints.
304+
*/
305+
CLIENT_HINTS("clientHints"),
306+
307+
/**
308+
* Clear cookies.
309+
*/
310+
COOKIES("cookies"),
311+
312+
/**
313+
* Clear execution contexts
314+
*/
315+
EXECUTION_CONTEXTS("executionContexts"),
316+
317+
/**
318+
* Clear storage
319+
*/
320+
STORAGE("storage"),
321+
322+
/**
323+
* Clear all types of data
324+
*/
325+
WILDCARD("*");
326+
327+
private String directive;
328+
329+
private ClearSiteData(String directive) {
330+
this.directive = directive;
331+
}
332+
333+
public String directive() {
334+
return "\"" + directive + "\"";
335+
}
336+
}
337+
338+
/**
339+
* Clear-Site-Data header directives
340+
*/
341+
Optional<Set<ClearSiteData>> clearSiteData();
294342
}
295343

296344
interface Backchannel {

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.LinkedList;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.Set;
2223
import java.util.SortedMap;
2324
import java.util.StringTokenizer;
2425
import java.util.TreeMap;
@@ -50,6 +51,7 @@
5051
import io.quarkus.oidc.common.runtime.OidcConstants;
5152
import io.quarkus.oidc.runtime.OidcTenantConfig.ApplicationType;
5253
import io.quarkus.oidc.runtime.OidcTenantConfig.Authentication;
54+
import io.quarkus.oidc.runtime.OidcTenantConfig.Logout.ClearSiteData;
5355
import io.quarkus.oidc.runtime.OidcTenantConfig.Roles;
5456
import io.quarkus.oidc.runtime.OidcTenantConfig.Token;
5557
import io.quarkus.oidc.runtime.providers.KnownOidcProviders;
@@ -104,6 +106,7 @@ public final class OidcUtils {
104106
public static final String DPOP_PROOF = "dpop_proof";
105107
public static final String DPOP_PROOF_JWT_HEADERS = "dpop_proof_jwt_headers";
106108
public static final String DPOP_PROOF_JWT_CLAIMS = "dpop_proof_jwt_claims";
109+
public static final String CLEAR_SITE_DATA_HEADER = "Clear-Site-Data";
107110

108111
private static final String APPLICATION_JWT = "application/jwt";
109112

@@ -841,4 +844,18 @@ public static boolean isApplicationJwtContentType(String ct) {
841844
String remainder = ct.substring(APPLICATION_JWT.length()).trim();
842845
return remainder.indexOf(';') == 0;
843846
}
847+
848+
public static void setClearSiteData(RoutingContext context, OidcTenantConfig oidcConfig) {
849+
Set<ClearSiteData> dirs = oidcConfig.logout().clearSiteData().orElse(Set.of());
850+
if (!dirs.isEmpty()) {
851+
StringBuilder builder = new StringBuilder();
852+
for (ClearSiteData dir : dirs) {
853+
if (!builder.isEmpty()) {
854+
builder.append(",");
855+
}
856+
builder.append(dir.directive());
857+
}
858+
context.response().putHeader(CLEAR_SITE_DATA_HEADER, builder.toString());
859+
}
860+
}
844861
}

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/LogoutConfigBuilder.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@
22

33
import java.time.Duration;
44
import java.util.HashMap;
5+
import java.util.HashSet;
6+
import java.util.List;
57
import java.util.Map;
68
import java.util.Objects;
79
import java.util.Optional;
10+
import java.util.Set;
811

912
import io.quarkus.oidc.OidcTenantConfigBuilder;
1013
import io.quarkus.oidc.runtime.OidcTenantConfig;
14+
import io.quarkus.oidc.runtime.OidcTenantConfig.Logout.ClearSiteData;
1115

1216
/**
1317
* Builder for the {@link OidcTenantConfig.Logout}.
1418
*/
1519
public final class LogoutConfigBuilder {
1620
private record LogoutImpl(Optional<String> path, Optional<String> postLogoutPath, String postLogoutUriParam,
1721
Map<String, String> extraParams, OidcTenantConfig.Backchannel backchannel,
18-
OidcTenantConfig.Frontchannel frontchannel) implements OidcTenantConfig.Logout {
22+
OidcTenantConfig.Frontchannel frontchannel,
23+
Optional<Set<ClearSiteData>> clearSiteData) implements OidcTenantConfig.Logout {
1924
}
2025

2126
private record FrontchannelImpl(Optional<String> path) implements OidcTenantConfig.Frontchannel {
@@ -28,6 +33,7 @@ private record FrontchannelImpl(Optional<String> path) implements OidcTenantConf
2833
private String postLogoutUriParam;
2934
private OidcTenantConfig.Backchannel backchannel;
3035
private OidcTenantConfig.Frontchannel frontchannel;
36+
private Optional<Set<ClearSiteData>> clearSiteData = Optional.of(new HashSet<>());
3137

3238
public LogoutConfigBuilder() {
3339
this(new OidcTenantConfigBuilder());
@@ -44,6 +50,7 @@ public LogoutConfigBuilder(OidcTenantConfigBuilder builder) {
4450
this.postLogoutUriParam = logout.postLogoutUriParam();
4551
this.backchannel = logout.backchannel();
4652
this.frontchannel = logout.frontchannel();
53+
this.clearSiteData = logout.clearSiteData();
4754
}
4855

4956
/**
@@ -105,6 +112,26 @@ public LogoutConfigBuilder extraParams(Map<String, String> extraParams) {
105112
return this;
106113
}
107114

115+
/**
116+
* Clear all site data
117+
*
118+
* @return this builder
119+
*/
120+
public LogoutConfigBuilder clearSiteData() {
121+
this.clearSiteData(List.of(ClearSiteData.WILDCARD));
122+
return this;
123+
}
124+
125+
/**
126+
* @param clear site data directives {@link OidcTenantConfig.Logout#clearSiteData()}
127+
* @return this builder
128+
*/
129+
public LogoutConfigBuilder clearSiteData(List<ClearSiteData> directives) {
130+
Objects.requireNonNull(directives);
131+
this.clearSiteData.get().addAll(directives);
132+
return this;
133+
}
134+
108135
/**
109136
* @param backchannel {@link OidcTenantConfig.Logout#backchannel()}
110137
* @return this builder
@@ -134,7 +161,8 @@ public OidcTenantConfigBuilder end() {
134161
* @return built {@link OidcTenantConfig.Logout}
135162
*/
136163
public OidcTenantConfig.Logout build() {
137-
return new LogoutImpl(path, postLogoutPath, postLogoutUriParam, Map.copyOf(extraParams), backchannel, frontchannel);
164+
return new LogoutImpl(path, postLogoutPath, postLogoutUriParam, Map.copyOf(extraParams), backchannel, frontchannel,
165+
clearSiteData);
138166
}
139167

140168
/**

extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Map;
88
import java.util.Optional;
99
import java.util.OptionalInt;
10+
import java.util.Set;
1011

1112
import io.quarkus.runtime.configuration.TrimmedStringConverter;
1213
import io.smallrye.config.WithConverter;
@@ -160,15 +161,16 @@ enum ConfigMappingMethods {
160161
LOGOUT_PATH,
161162
LOGOUT_POST_LOGOUT_PATH,
162163
LOGOUT_POST_LOGOUT_URI_PARAM,
163-
LOGOUT_POST_LOGOUT_EXTRA_PARAMS,
164-
LOGOUT_POST_LOGOUT_BACK_CHANNEL,
165-
LOGOUT_POST_LOGOUT_FRONT_CHANNEL,
166-
LOGOUT_POST_LOGOUT_FRONT_CHANNEL_PATH,
167-
LOGOUT_POST_LOGOUT_BACK_CHANNEL_PATH,
168-
LOGOUT_POST_LOGOUT_BACK_CHANNEL_TOKEN_CACHE_SIZE,
169-
LOGOUT_POST_LOGOUT_BACK_CHANNEL_TOKEN_CACHE_TTL,
170-
LOGOUT_POST_LOGOUT_BACK_CHANNEL_CLEAN_UP_TIMER_INTERVAL,
171-
LOGOUT_POST_LOGOUT_BACK_CHANNEL_LOGOUT_TOKEN_KEY,
164+
LOGOUT_CLEAR_SITE_DATA,
165+
LOGOUT_EXTRA_PARAMS,
166+
LOGOUT_BACK_CHANNEL,
167+
LOGOUT_FRONT_CHANNEL,
168+
LOGOUT_FRONT_CHANNEL_PATH,
169+
LOGOUT_BACK_CHANNEL_PATH,
170+
LOGOUT_BACK_CHANNEL_TOKEN_CACHE_SIZE,
171+
LOGOUT_BACK_CHANNEL_TOKEN_CACHE_TTL,
172+
LOGOUT_BACK_CHANNEL_CLEAN_UP_TIMER_INTERVAL,
173+
LOGOUT_BACK_CHANNEL_LOGOUT_TOKEN_KEY,
172174
TOKEN_ISSUER,
173175
TOKEN_AUDIENCE,
174176
TOKEN_SUBJECT_REQUIRED,
@@ -480,54 +482,60 @@ public String postLogoutUriParam() {
480482

481483
@Override
482484
public Map<String, String> extraParams() {
483-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_EXTRA_PARAMS, true);
485+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_EXTRA_PARAMS, true);
484486
return Map.of();
485487
}
486488

489+
@Override
490+
public Optional<Set<ClearSiteData>> clearSiteData() {
491+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_CLEAR_SITE_DATA, true);
492+
return Optional.of(Set.of());
493+
}
494+
487495
@Override
488496
public Backchannel backchannel() {
489-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL, true);
497+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL, true);
490498
return new Backchannel() {
491499
@Override
492500
public Optional<String> path() {
493-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL_PATH, true);
501+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL_PATH, true);
494502
return Optional.empty();
495503
}
496504

497505
@Override
498506
public int tokenCacheSize() {
499-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL_TOKEN_CACHE_SIZE, true);
507+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL_TOKEN_CACHE_SIZE, true);
500508
return 0;
501509
}
502510

503511
@Override
504512
public Duration tokenCacheTimeToLive() {
505-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL_TOKEN_CACHE_TTL, true);
513+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL_TOKEN_CACHE_TTL, true);
506514
return null;
507515
}
508516

509517
@Override
510518
public Optional<Duration> cleanUpTimerInterval() {
511-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL_CLEAN_UP_TIMER_INTERVAL,
519+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL_CLEAN_UP_TIMER_INTERVAL,
512520
true);
513521
return Optional.empty();
514522
}
515523

516524
@Override
517525
public String logoutTokenKey() {
518-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_BACK_CHANNEL_LOGOUT_TOKEN_KEY, true);
526+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_BACK_CHANNEL_LOGOUT_TOKEN_KEY, true);
519527
return "";
520528
}
521529
};
522530
}
523531

524532
@Override
525533
public Frontchannel frontchannel() {
526-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_FRONT_CHANNEL, true);
534+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_FRONT_CHANNEL, true);
527535
return new Frontchannel() {
528536
@Override
529537
public Optional<String> path() {
530-
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_POST_LOGOUT_FRONT_CHANNEL_PATH, true);
538+
invocationsRecorder.put(ConfigMappingMethods.LOGOUT_FRONT_CHANNEL_PATH, true);
531539
return Optional.empty();
532540
}
533541
};

integration-tests/oidc-wiremock-logout/src/main/resources/application.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ quarkus.oidc.code-flow-form-post.revoke-path=${keycloak.url}/realms/quarkus/revo
1414
# reuse the wiremock JWK endpoint stub for the `quarkus` realm - it is the same for the query and form post response mode
1515
quarkus.oidc.code-flow-form-post.jwks-path=${keycloak.url}/realms/quarkus/protocol/openid-connect/certs
1616
quarkus.oidc.code-flow-form-post.logout.backchannel.path=/back-channel-logout
17+
quarkus.oidc.code-flow-form-post.logout.clear-site-data=cache,cookies
1718
quarkus.oidc.code-flow-form-post.token.audience=https://server.example.com,https://id.server.example.com
1819

1920
quarkus.native.additional-build-args=-H:IncludeResources=private.*\\.*,-H:IncludeResources=.*\\.p12

integration-tests/oidc-wiremock-logout/src/test/java/io/quarkus/it/keycloak/CodeFlowAuthorizationTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static org.junit.jupiter.api.Assertions.assertEquals;
88
import static org.junit.jupiter.api.Assertions.assertNotNull;
99
import static org.junit.jupiter.api.Assertions.assertNull;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
1011

1112
import java.net.URI;
1213
import java.time.Duration;
@@ -94,6 +95,8 @@ public void testCodeFlowFormPostAndBackChannelLogout() throws Exception {
9495
WebResponse webResponse = webClient
9596
.loadWebResponse(new WebRequest(URI.create("http://localhost:8081/service/code-flow-form-post").toURL()));
9697
assertEquals(302, webResponse.getStatusCode());
98+
String clearSiteData = webResponse.getResponseHeaderValue("clear-site-data");
99+
assertTrue(clearSiteData.equals("\"cache\",\"cookies\"") || clearSiteData.equals("\"cookies\",\"cache\""));
97100

98101
assertNull(getSessionCookie(webClient, "code-flow-form-post"));
99102

0 commit comments

Comments
 (0)