newCookies;
public QuarkusHttpResponse(ResteasyReactiveRequestContext requestContext) {
this.requestContext = requestContext;
@@ -58,18 +61,18 @@ public final class QuarkusHttpResponse implements HttpResponse {
}
@Override
- public void setCookieIfAbsent(HttpCookie cookie) {
- if (cookie == null) {
+ public void setCookieIfAbsent(NewCookie newCookie) {
+ if (newCookie == null) {
throw new IllegalArgumentException("Cookie is null");
}
- if (cookies == null) {
- cookies = new HashSet<>();
+ if (newCookies == null) {
+ newCookies = new HashSet<>();
}
- if (cookies.add(cookie)) {
- addHeader(HttpHeaders.SET_COOKIE, cookie.toHeaderValue());
+ if (newCookies.add(newCookie)) {
+ String headerValue = NEW_COOKIE_HEADER_DELEGATE.toString(newCookie);
+ addHeader(HttpHeaders.SET_COOKIE, headerValue);
}
}
-
}
diff --git a/server-spi-private/src/main/java/org/keycloak/cookie/CookieScope.java b/server-spi-private/src/main/java/org/keycloak/cookie/CookieScope.java
index 79ce56fb3b..fa8f16853c 100644
--- a/server-spi-private/src/main/java/org/keycloak/cookie/CookieScope.java
+++ b/server-spi-private/src/main/java/org/keycloak/cookie/CookieScope.java
@@ -1,17 +1,17 @@
package org.keycloak.cookie;
-import org.keycloak.common.util.ServerCookie;
+import jakarta.ws.rs.core.NewCookie;
public enum CookieScope {
// Internal cookies are only available for direct requests to Keycloak
- INTERNAL(ServerCookie.SameSiteAttributeValue.STRICT, true),
+ INTERNAL(NewCookie.SameSite.STRICT, true),
// Federation cookies are available after redirect from applications, and are also available in an iframe context
// unless the browser blocks third-party cookies
- FEDERATION(ServerCookie.SameSiteAttributeValue.NONE, true),
+ FEDERATION(NewCookie.SameSite.NONE, true),
// Federation cookies that are also available from JavaScript
- FEDERATION_JS(ServerCookie.SameSiteAttributeValue.NONE, false),
+ FEDERATION_JS(NewCookie.SameSite.NONE, false),
// Legacy cookies do not set the SameSite attribute and will default to SameSite=Lax in modern browsers
@Deprecated
@@ -21,15 +21,15 @@ public enum CookieScope {
@Deprecated
LEGACY_JS(null, false);
- private final ServerCookie.SameSiteAttributeValue sameSite;
+ private final NewCookie.SameSite sameSite;
private final boolean httpOnly;
- CookieScope(ServerCookie.SameSiteAttributeValue sameSite, boolean httpOnly) {
+ CookieScope(NewCookie.SameSite sameSite, boolean httpOnly) {
this.sameSite = sameSite;
this.httpOnly = httpOnly;
}
- public ServerCookie.SameSiteAttributeValue getSameSite() {
+ public NewCookie.SameSite getSameSite() {
return sameSite;
}
diff --git a/server-spi/src/main/java/org/keycloak/http/HttpCookie.java b/server-spi/src/main/java/org/keycloak/http/HttpCookie.java
index 4ee3b8c238..a142ab29ca 100644
--- a/server-spi/src/main/java/org/keycloak/http/HttpCookie.java
+++ b/server-spi/src/main/java/org/keycloak/http/HttpCookie.java
@@ -17,33 +17,36 @@
package org.keycloak.http;
-import org.keycloak.common.util.ServerCookie;
+import jakarta.ws.rs.core.NewCookie;
+import jakarta.ws.rs.ext.RuntimeDelegate;
import org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
/**
* An extension of {@link javax.ws.rs.core.Cookie} in order to support additional
* fields and behavior.
+ *
+ * @deprecated This class will be removed in the future. Please use {@link jakarta.ws.rs.core.NewCookie.Builder}
*/
-public final class HttpCookie extends jakarta.ws.rs.core.Cookie {
-
- private final String comment;
- private final int maxAge;
- private final boolean secure;
- private final boolean httpOnly;
- private final SameSiteAttributeValue sameSite;
+@Deprecated(since = "24.0.0", forRemoval = true)
+public final class HttpCookie extends NewCookie {
public HttpCookie(int version, String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly, SameSiteAttributeValue sameSite) {
- super(name, value, path, domain, version);
- this.comment = comment;
- this.maxAge = maxAge;
- this.secure = secure;
- this.httpOnly = httpOnly;
- this.sameSite = sameSite;
+ super(name, value, path, domain, version, comment, maxAge, null, secure, httpOnly, convertSameSite(sameSite));
+ }
+
+ private static SameSite convertSameSite(SameSiteAttributeValue sameSiteAttributeValue) {
+ if (sameSiteAttributeValue == null) {
+ return null;
+ }
+ switch (sameSiteAttributeValue) {
+ case NONE: return SameSite.NONE;
+ case LAX: return SameSite.LAX;
+ case STRICT: return SameSite.STRICT;
+ }
+ throw new IllegalArgumentException("Unknown SameSite value " + sameSiteAttributeValue);
}
public String toHeaderValue() {
- StringBuilder cookieBuf = new StringBuilder();
- ServerCookie.appendCookieValue(cookieBuf, getVersion(), getName(), getValue(), getPath(), getDomain(), comment, maxAge, secure, httpOnly, sameSite);
- return cookieBuf.toString();
+ return RuntimeDelegate.getInstance().createHeaderDelegate(NewCookie.class).toString(this);
}
}
diff --git a/server-spi/src/main/java/org/keycloak/http/HttpResponse.java b/server-spi/src/main/java/org/keycloak/http/HttpResponse.java
index 85c0da03cf..359de398aa 100644
--- a/server-spi/src/main/java/org/keycloak/http/HttpResponse.java
+++ b/server-spi/src/main/java/org/keycloak/http/HttpResponse.java
@@ -17,6 +17,8 @@
package org.keycloak.http;
+import jakarta.ws.rs.core.NewCookie;
+
/**
* Represents an out coming HTTP response.
*
@@ -56,6 +58,17 @@ public interface HttpResponse {
*
* @param cookie the cookie
*/
- void setCookieIfAbsent(HttpCookie cookie);
+ void setCookieIfAbsent(NewCookie cookie);
+
+ /**
+ * Sets a new cookie only if not yet set.
+ * @deprecated This method will be removed in the future. Please use {@link jakarta.ws.rs.core.NewCookie.Builder}
+ *
+ * @param cookie the cookie
+ */
+ @Deprecated(since = "24.0.0", forRemoval = true)
+ default void setCookieIfAbsent(HttpCookie cookie) {
+ setCookieIfAbsent((NewCookie) cookie);
+ }
}
diff --git a/services/src/main/java/org/keycloak/cookie/CookiePathResolver.java b/services/src/main/java/org/keycloak/cookie/CookiePathResolver.java
new file mode 100644
index 0000000000..f161552fc2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/cookie/CookiePathResolver.java
@@ -0,0 +1,34 @@
+package org.keycloak.cookie;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.services.resources.RealmsResource;
+
+class CookiePathResolver {
+
+ private final KeycloakContext context;
+ private String realmPath;
+
+ private String requestPath;
+
+ CookiePathResolver(KeycloakContext context) {
+ this.context = context;
+ }
+
+ String resolvePath(CookieType cookieType) {
+ switch (cookieType.getPath()) {
+ case REALM:
+ if (realmPath == null) {
+ realmPath = RealmsResource.realmBaseUrl(context.getUri()).path("/").build(context.getRealm().getName()).getRawPath();
+ }
+ return realmPath;
+ case REQUEST:
+ if (requestPath == null) {
+ requestPath = context.getUri().getRequestUri().getRawPath();
+ }
+ return requestPath;
+ default:
+ throw new IllegalArgumentException("Unsupported enum value " + cookieType.getPath().name());
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/cookie/CookieSecureResolver.java b/services/src/main/java/org/keycloak/cookie/CookieSecureResolver.java
new file mode 100644
index 0000000000..d0c732f95d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/cookie/CookieSecureResolver.java
@@ -0,0 +1,39 @@
+package org.keycloak.cookie;
+
+import jakarta.ws.rs.core.NewCookie;
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.RealmModel;
+
+import java.net.URI;
+
+class CookieSecureResolver {
+
+ private final KeycloakContext context;
+ private final boolean sameSiteLegacyEnabled;
+
+ CookieSecureResolver(KeycloakContext context, boolean sameSiteLegacyEnabled) {
+ this.context = context;
+ this.sameSiteLegacyEnabled = sameSiteLegacyEnabled;
+ }
+
+ boolean resolveSecure(NewCookie.SameSite sameSite) {
+ // Due to cookies with SameSite=none secure context is always required when same-site legacy cookies are disabled
+ if (!sameSiteLegacyEnabled) {
+ return true;
+ } else {
+ // SameSite=none requires secure context
+ if (NewCookie.SameSite.NONE.equals(sameSite)) {
+ return true;
+ }
+
+ URI requestUri = context.getUri().getRequestUri();
+ RealmModel realm = context.getRealm();
+ if (realm != null && realm.getSslRequired().isRequired(requestUri.getHost())) {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/cookie/DefaultCookieProvider.java b/services/src/main/java/org/keycloak/cookie/DefaultCookieProvider.java
index e8fc33f12d..6ba907473a 100644
--- a/services/src/main/java/org/keycloak/cookie/DefaultCookieProvider.java
+++ b/services/src/main/java/org/keycloak/cookie/DefaultCookieProvider.java
@@ -1,31 +1,36 @@
package org.keycloak.cookie;
import jakarta.ws.rs.core.Cookie;
+import jakarta.ws.rs.core.NewCookie;
import org.jboss.logging.Logger;
-import org.keycloak.common.util.ServerCookie;
-import org.keycloak.http.HttpCookie;
import org.keycloak.models.KeycloakContext;
-import org.keycloak.models.RealmModel;
-import org.keycloak.services.resources.RealmsResource;
-import org.keycloak.urls.UrlType;
-import java.net.URI;
import java.util.Map;
-
public class DefaultCookieProvider implements CookieProvider {
private static final Logger logger = Logger.getLogger(DefaultCookieProvider.class);
private final KeycloakContext context;
+ private CookiePathResolver pathResolver;
+
+ private CookieSecureResolver secureResolver;
+
private final Map cookies;
- private final boolean legacyCookiesEnabled;
+ private final boolean sameSiteLegacyEnabled;
- public DefaultCookieProvider(KeycloakContext context, boolean legacyCookiesEnabled) {
+ public DefaultCookieProvider(KeycloakContext context, boolean sameSiteLegacyEnabled) {
this.context = context;
this.cookies = context.getRequestHeaders().getCookies();
- this.legacyCookiesEnabled = legacyCookiesEnabled;
+ this.pathResolver = new CookiePathResolver(context);
+ this.secureResolver = new CookieSecureResolver(context, sameSiteLegacyEnabled);
+ this.sameSiteLegacyEnabled = sameSiteLegacyEnabled;
+
+ if (logger.isTraceEnabled()) {
+ String cookieNames = String.join(", ", this.cookies.keySet());
+ logger.tracef("Path: %s, cookies: %s", context.getUri().getRequestUri().getRawPath(), cookieNames);
+ }
}
@Override
@@ -40,59 +45,90 @@ public class DefaultCookieProvider implements CookieProvider {
@Override
public void set(CookieType cookieType, String value, int maxAge) {
String name = cookieType.getName();
- ServerCookie.SameSiteAttributeValue sameSite = cookieType.getScope().getSameSite();
- boolean secure = resolveSecure(sameSite);
- String path = resolvePath(cookieType);
+ NewCookie.SameSite sameSite = cookieType.getScope().getSameSite();
+ boolean secure = secureResolver.resolveSecure(sameSite);
+ String path = pathResolver.resolvePath(cookieType);
boolean httpOnly = cookieType.getScope().isHttpOnly();
- HttpCookie newCookie = new HttpCookie(1, name, value, path, null, null, maxAge, secure, httpOnly, sameSite);
+ NewCookie newCookie = new NewCookie.Builder(name)
+ .version(1)
+ .value(value)
+ .path(path)
+ .maxAge(maxAge)
+ .secure(secure)
+ .httpOnly(httpOnly)
+ .sameSite(sameSite)
+ .build();
+
context.getHttpResponse().setCookieIfAbsent(newCookie);
logger.tracef("Setting cookie: name: %s, path: %s, same-site: %s, secure: %s, http-only: %s, max-age: %d", name, path, sameSite, secure, httpOnly, maxAge);
- if (legacyCookiesEnabled && cookieType.supportsSameSiteLegacy()) {
- if (ServerCookie.SameSiteAttributeValue.NONE.equals(sameSite)) {
- secure = resolveSecure(null);
- String legacyName = cookieType.getSameSiteLegacyName();
- HttpCookie legacyCookie = new HttpCookie(1, legacyName, value, path, null, null, maxAge, secure, httpOnly, null);
- context.getHttpResponse().setCookieIfAbsent(legacyCookie);
+ setSameSiteLegacy(cookieType, value, maxAge);
+ }
- logger.tracef("Setting legacy cookie: name: %s, path: %s, same-site: %s, secure: %s, http-only: %s, max-age: %d", legacyName, path, sameSite, secure, httpOnly, maxAge);
- }
- } else {
- expireLegacy(cookieType);
+ private void setSameSiteLegacy(CookieType cookieType, String value, int maxAge) {
+ if (sameSiteLegacyEnabled && cookieType.supportsSameSiteLegacy()) {
+ String legacyName = cookieType.getSameSiteLegacyName();
+ boolean legacySecure = secureResolver.resolveSecure(null);
+ String path = pathResolver.resolvePath(cookieType);
+ boolean httpOnly = cookieType.getScope().isHttpOnly();
+
+ NewCookie legacyCookie = new NewCookie.Builder(legacyName)
+ .version(1)
+ .value(value)
+ .maxAge(maxAge)
+ .path(path)
+ .secure(legacySecure)
+ .httpOnly(httpOnly)
+ .build();
+ context.getHttpResponse().setCookieIfAbsent(legacyCookie);
+
+ logger.tracef("Setting legacy cookie: name: %s, path: %s, same-site: %s, secure: %s, http-only: %s, max-age: %d", legacyName, path, null, legacySecure, httpOnly, maxAge);
+ } else if (cookieType.supportsSameSiteLegacy()) {
+ expireSameSiteLegacy(cookieType);
}
}
@Override
public String get(CookieType cookieType) {
Cookie cookie = cookies.get(cookieType.getName());
- if (cookie == null && cookieType.supportsSameSiteLegacy()) {
- cookie = cookies.get(cookieType.getSameSiteLegacyName());
+ if (cookie == null) {
+ cookie = getSameSiteLegacyCookie(cookieType);
}
return cookie != null ? cookie.getValue() : null;
}
- @Override
- public void expire(CookieType cookieType) {
- Cookie cookie = cookies.get(cookieType.getName());
- expire(cookie, cookieType);
-
- expireLegacy(cookieType);
- }
-
- private void expireLegacy(CookieType cookieType) {
+ private Cookie getSameSiteLegacyCookie(CookieType cookieType) {
if (cookieType.supportsSameSiteLegacy()) {
- String legacyName = cookieType.getSameSiteLegacyName();
- Cookie legacyCookie = cookies.get(legacyName);
- expire(legacyCookie, cookieType);
+ return cookies.get(cookieType.getSameSiteLegacyName());
+ } else {
+ return null;
}
}
- private void expire(Cookie cookie, CookieType cookieType) {
+ @Override
+ public void expire(CookieType cookieType) {
+ expire(cookieType.getName(), cookieType);
+ expireSameSiteLegacy(cookieType);
+ }
+
+ private void expireSameSiteLegacy(CookieType cookieType) {
+ if (cookieType.supportsSameSiteLegacy()) {
+ expire(cookieType.getSameSiteLegacyName(), cookieType);
+ }
+ }
+
+ private void expire(String cookieName, CookieType cookieType) {
+ Cookie cookie = cookies.get(cookieName);
if (cookie != null) {
- String path = resolvePath(cookieType);
- HttpCookie newCookie = new HttpCookie(1, cookie.getName(), "", path, null, null, CookieMaxAge.EXPIRED, false, false, null);
+ String path = pathResolver.resolvePath(cookieType);
+ NewCookie newCookie = new NewCookie.Builder(cookieName)
+ .version(1)
+ .path(path)
+ .maxAge(CookieMaxAge.EXPIRED)
+ .build();
+
context.getHttpResponse().setCookieIfAbsent(newCookie);
logger.tracef("Expiring cookie: name: %s, path: %s", cookie.getName(), path);
@@ -103,31 +139,4 @@ public class DefaultCookieProvider implements CookieProvider {
public void close() {
}
- private String resolvePath(CookieType cookieType) {
- switch (cookieType.getPath()) {
- case REALM:
- return RealmsResource.realmBaseUrl(context.getUri()).path("/").build(context.getRealm().getName()).getRawPath();
- case REQUEST:
- return context.getUri().getRequestUri().getRawPath();
- default:
- throw new IllegalArgumentException("Unsupported enum value " + cookieType.getPath().name());
- }
- }
-
- private boolean resolveSecure(ServerCookie.SameSiteAttributeValue sameSite) {
- URI requestUri = context.getUri().getRequestUri();
-
- // SameSite=none requires secure context
- if (ServerCookie.SameSiteAttributeValue.NONE.equals(sameSite)) {
- return true;
- }
-
- RealmModel realm = context.getRealm();
- if (realm != null && realm.getSslRequired().isRequired(requestUri.getHost())) {
- return true;
- }
-
- return false;
- }
-
}
diff --git a/services/src/main/java/org/keycloak/cookie/DefaultCookieProviderFactory.java b/services/src/main/java/org/keycloak/cookie/DefaultCookieProviderFactory.java
index 31b3f1e42e..e7ec2c10c8 100644
--- a/services/src/main/java/org/keycloak/cookie/DefaultCookieProviderFactory.java
+++ b/services/src/main/java/org/keycloak/cookie/DefaultCookieProviderFactory.java
@@ -3,19 +3,24 @@ package org.keycloak.cookie;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+
+import java.util.List;
public class DefaultCookieProviderFactory implements CookieProviderFactory {
- private boolean legacyCookies;
+ private static final String SAME_SITE_LEGACY_KEY = "sameSiteLegacy";
+ private boolean sameSiteLegacyEnabled;
@Override
public CookieProvider create(KeycloakSession session) {
- return new DefaultCookieProvider(session.getContext(), legacyCookies);
+ return new DefaultCookieProvider(session.getContext(), sameSiteLegacyEnabled);
}
@Override
public void init(Config.Scope config) {
- legacyCookies = config.getBoolean("legacyCookies", true);
+ sameSiteLegacyEnabled = config.getBoolean(SAME_SITE_LEGACY_KEY, true);
}
@Override
@@ -31,4 +36,15 @@ public class DefaultCookieProviderFactory implements CookieProviderFactory {
return "default";
}
+ @Override
+ public List getConfigMetadata() {
+ return ProviderConfigurationBuilder.create()
+ .property()
+ .name(SAME_SITE_LEGACY_KEY)
+ .type("boolean")
+ .helpText("Adds legacy cookies without SameSite parameter")
+ .defaultValue(true)
+ .add()
+ .build();
+ }
}
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/AuthenticationStateCookie.java b/services/src/main/java/org/keycloak/forms/login/freemarker/AuthenticationStateCookie.java
index 1cfce9d05d..acaaa6aecd 100644
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/AuthenticationStateCookie.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/AuthenticationStateCookie.java
@@ -24,6 +24,7 @@ import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.Encode;
import org.keycloak.cookie.CookieProvider;
import org.keycloak.cookie.CookieType;
import org.keycloak.models.KeycloakSession;
@@ -69,7 +70,7 @@ public class AuthenticationStateCookie {
cookie.setRemainingTabs(rootAuthSession.getAuthenticationSessions().keySet());
try {
- String encoded = JsonSerialization.writeValueAsString(cookie);
+ String encoded = Encode.urlEncode(JsonSerialization.writeValueAsString(cookie));
session.getProvider(CookieProvider.class).set(CookieType.AUTH_STATE, encoded, cookieMaxAge);
} catch (IOException ioe) {
throw new IllegalStateException("Exception thrown when encoding cookie", ioe);
diff --git a/services/src/main/java/org/keycloak/services/HttpResponseImpl.java b/services/src/main/java/org/keycloak/services/HttpResponseImpl.java
index f391a2c512..a7d6f00f01 100644
--- a/services/src/main/java/org/keycloak/services/HttpResponseImpl.java
+++ b/services/src/main/java/org/keycloak/services/HttpResponseImpl.java
@@ -17,8 +17,7 @@
package org.keycloak.services;
-import jakarta.ws.rs.core.HttpHeaders;
-import org.keycloak.http.HttpCookie;
+import jakarta.ws.rs.core.NewCookie;
import org.keycloak.http.HttpResponse;
import java.util.HashSet;
@@ -27,7 +26,7 @@ import java.util.Set;
public class HttpResponseImpl implements HttpResponse {
private final org.jboss.resteasy.spi.HttpResponse delegate;
- private Set cookies;
+ private Set newCookies;
public HttpResponseImpl(org.jboss.resteasy.spi.HttpResponse delegate) {
this.delegate = delegate;
@@ -54,17 +53,17 @@ public class HttpResponseImpl implements HttpResponse {
}
@Override
- public void setCookieIfAbsent(HttpCookie cookie) {
- if (cookie == null) {
+ public void setCookieIfAbsent(NewCookie newCookie) {
+ if (newCookie == null) {
throw new IllegalArgumentException("Cookie is null");
}
- if (cookies == null) {
- cookies = new HashSet<>();
+ if (newCookies == null) {
+ newCookies = new HashSet<>();
}
- if (cookies.add(cookie)) {
- addHeader(HttpHeaders.SET_COOKIE, cookie.toHeaderValue());
+ if (newCookies.add(newCookie)) {
+ delegate.addNewCookie(newCookie);
}
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
index fdd4a13032..241501028a 100644
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
@@ -17,9 +17,7 @@
package org.keycloak.services.managers;
-import jakarta.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
-import org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
import org.keycloak.common.util.Time;
import org.keycloak.cookie.CookieProvider;
import org.keycloak.cookie.CookieType;
diff --git a/services/src/main/resources/org/keycloak/protocol/oidc/endpoints/login-status-iframe.html b/services/src/main/resources/org/keycloak/protocol/oidc/endpoints/login-status-iframe.html
index 649c9f11f9..ee640cffa0 100755
--- a/services/src/main/resources/org/keycloak/protocol/oidc/endpoints/login-status-iframe.html
+++ b/services/src/main/resources/org/keycloak/protocol/oidc/endpoints/login-status-iframe.html
@@ -109,14 +109,13 @@
}
function getCookieByName(name) {
- const cookies = new Map();
-
for (const cookie of document.cookie.split(";")) {
const [key, value] = cookie.split("=").map((value) => value.trim());
- cookies.set(key, value);
+ if (key === name) {
+ return value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
+ }
}
-
- return cookies.get(name) ?? null;
+ return null;
}