apereo/cas · warning
User Agent header [ ] is not supported in the list of…
Error message
User Agent header [{}] is not supported in the list of supported browsers [{}] What it means
SpnegoNegotiateCredentialsAction only initiates SPNEGO negotiation for user agents on the configured supported-browsers list. If the request's User-Agent header is empty or does not match any configured supported browser pattern, it logs this warning and returns the webflow error so a normal (form) login proceeds. This is the deliberate gate that keeps SPNEGO for known-capable browsers.
Solutions
- Add the client's user-agent pattern to cas.authn.spnego.supported-browsers (regex list).
- If the client is a script/app, either bypass SPNEGO or use an alternative auth mechanism (Basic/OAuth) instead of forcing Negotiate.
- Check that proxies do not strip or rewrite the User-Agent header.
- Keep the default wildcard-inclusive patterns if you want broad browser coverage.
Example fix
// before cas.authn.spnego.supported-browsers=MSIE,Trident // after cas.authn.spnego.supported-browsers=MSIE,Trident,Firefox,AppleWebKit,Chrome,Edg
Defensive patterns
Strategy: validation
Validate before calling
String ua = request.getHeader("User-Agent");
boolean supported = supportedBrowsers.stream().anyMatch(p -> ua != null && ua.matches(p));
if (!supported) { /* route to form login instead of negotiate */ } Type guard
static boolean hasUserAgent(HttpServletRequest r) {
String ua = r.getHeader("User-Agent");
return ua != null && !ua.isBlank();
} Prevention
- Keep supported-browsers patterns broad and current
- Don't strip User-Agent at the edge proxy
- Use form-login fallback for scripts/APIs
When it happens
Trigger: doExecuteInternal receives a request whose User-Agent is blank, or cas.authn.spnego[...].supportedBrowsers patterns (default targeting MSIE/Trident, Firefox, AppleWebKit) do not regex-match the UA string, causing isSupportedBrowser to return false.
Common situations: Non-browser clients (curl, Java HttpClient, mobile apps) hitting CAS; very new or exotic browser UA strings not matching the default patterns; proxy/gateway scrubbing the User-Agent header; supportedBrowsers misconfigured to a too-narrow regex.
Related errors
- User Agent header [ ] is empty, or no browsers are supported
- No user can be accepted because none is defined
- Not all requested multifactor providers could be found…
- Cookie name is undefined
- Request does not specify a user-agent
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/a510f4fb100cd1a2.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-spnego-webflow/src/main/java/org/apereo/cas/web/flow/SpnegoNegotiateCredentialsAction.java:75
*/
private final boolean mixedModeAuthentication;
@Override
protected @Nullable Event doExecuteInternal(final RequestContext context) {
val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
val response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
val authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
val userAgent = HttpRequestUtils.getHttpServletRequestUserAgent(request);
LOGGER.debug("Authorization header [{}], User Agent header [{}]", authorizationHeader, userAgent);
if (!StringUtils.hasText(userAgent) || this.supportedBrowser.isEmpty()) {
LOGGER.warn("User Agent header [{}] is empty, or no browsers are supported", userAgent);
return error();
}
if (!isSupportedBrowser(userAgent)) {
LOGGER.warn("User Agent header [{}] is not supported in the list of supported browsers [{}]",
userAgent, this.supportedBrowser);
return error();
}
if (!StringUtils.hasText(authorizationHeader)
|| !authorizationHeader.startsWith(SpnegoConstants.NEGOTIATE)
|| authorizationHeader.length() <= SpnegoConstants.NEGOTIATE.length()) {
LOGGER.debug("Authorization header not found or does not match the message prefix [{}]. Sending [{}] header [{}]",
SpnegoConstants.NEGOTIATE, SpnegoConstants.HEADER_AUTHENTICATE, SpnegoConstants.NEGOTIATE);
response.setHeader(SpnegoConstants.HEADER_AUTHENTICATE, SpnegoConstants.NEGOTIATE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
/*
The responseComplete flag tells the pausing view-state not to render the response
because another object has taken care of it. If mixed mode authentication is allowed
then responseComplete should not be called so that webflow will display the login page.
*/View on GitHub (pinned to e7288fc434)