SecureContextResolver.java
package org.keycloak.utils;
import org.keycloak.device.DeviceRepresentationProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.account.DeviceRepresentation;
import java.net.URI;
import java.util.function.Supplier;
import java.util.regex.Pattern;
public class SecureContextResolver {
private static final Pattern LOCALHOST_IPV4 = Pattern.compile("127.\\d{1,3}.\\d{1,3}.\\d{1,3}");
/**
* Determines if a session is within a 'secure context', meaning its origin is considered potentially trustworthy by user-agents.
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts">MDN Web Docs ��� Secure Contexts</a>
* @see <a href="https://w3c.github.io/webappsec-secure-contexts/#algorithms">W3C Secure Contexts specification ��� Is origin potentially trustworthy?</a>
* @param session The session to check for trustworthiness.
* @return Whether the session can be considered potentially trustworthy by user-agents.
*/
public static boolean isSecureContext(KeycloakSession session) {
URI uri = session.getContext().getUri().getRequestUri();
// Use a Supplier so the user-agent is evaluated lazily, avoiding unnecessary parsing in production deployments.
Supplier<DeviceRepresentation> deviceRepresentationSupplier = () -> {
DeviceRepresentationProvider deviceRepresentationProvider = session.getProvider(DeviceRepresentationProvider.class);
return deviceRepresentationProvider.deviceRepresentation();
};
return isSecureContext(uri, deviceRepresentationSupplier);
}
static boolean isSecureContext(URI uri, Supplier<DeviceRepresentation> deviceRepresentationSupplier) {
if (uri.getScheme().equals("https")) {
return true;
}
DeviceRepresentation deviceRepresentation = deviceRepresentationSupplier.get();
String browser = deviceRepresentation != null ? deviceRepresentation.getBrowser() : null;
// Safari has a bug where even a secure context is not able to set cookies with the 'Secure' directive.
// Hence, we need to assume the worst case scenario and downgrade to an insecure context.
// See:
// - https://github.com/keycloak/keycloak/issues/33557
// - https://webcompat.com/issues/142566
// - https://bugs.webkit.org/show_bug.cgi?id=232088
// - https://bugs.webkit.org/show_bug.cgi?id=276313
if (browser != null && browser.toLowerCase().contains("safari")) {
return false;
}
String host = uri.getHost();
if (host == null) {
return false;
}
// The host matches a CIDR notation of ::1/128
if (host.equals("[::1]") || host.equals("[0000:0000:0000:0000:0000:0000:0000:0001]")) {
return true;
}
// The host matches a CIDR notation of 127.0.0.0/8
if (LOCALHOST_IPV4.matcher(host).matches()) {
return true;
}
if (host.equals("localhost") || host.equals("localhost.")) {
return true;
}
return host.endsWith(".localhost") || host.endsWith(".localhost.");
}
}