apereo/cas · warning
User Agent header [ ] is empty, or no browsers are supported
Error message
User Agent header [{}] is empty, or no browsers are supported What it means
SpnegoNegotiateCredentialsAction begins SPNEGO/WAFFLE negotiation only when the request has a non-empty User-Agent and at least one supported browser is configured. If either check fails it logs this warning and returns the error event, so the SPNEGO credentials action does not proceed. It guards against wasting a negotiation round-trip on clients that can never complete Kerberos/SPNEGO.
Solutions
- Configure cas.authn.spnego.supported-browsers with at least the default browser regex (e.g. MSIE.*, Firefox.*, AppleWebKit.*, etc.).
- Ensure clients attempting SPNEGO send a User-Agent header matching one of the supported browser patterns.
- Check any reverse proxy/load balancer is not stripping the User-Agent header before it reaches CAS.
- If non-browser clients must reach the login page, route them so they do not enter the SPNEGO flow, or relax the configuration deliberately.
Example fix
// before cas.authn.spnego.supported-browsers= // after cas.authn.spnego.supported-browsers=MSIE.*,Trident.*,Firefox.*,AppleWebKit.*,Chrome.*
Defensive patterns
Strategy: validation
Validate before calling
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
boolean spnegoEligible = StringUtils.hasText(userAgent)
&& supportedBrowsers.stream().anyMatch(ua -> userAgent.matches(ua)); Prevention
- Always populate supportedBrowsers in cas.authn.spnego settings.
- Test SPNEGO clients with the actual User-Agent headers they send.
- Audit reverse proxies for header stripping.
- Exclude non-browser clients from the SPNEGO flow.
When it happens
Trigger: Request reaches the SPNEGO webflow with no User-Agent header (missing header or a client that strips it), or cas.authn.spnego.supportedBrowsers resolves to an empty list so this.supportedBrowser is empty.
Common situations: Non-browser API clients/health probes hitting the login flow without a User-Agent; supportedBrowsers left unconfigured or regex list emptied in cas.properties; reverse proxy stripping the User-Agent header; embedded client libraries performing HTTP calls without UA headers.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- NTLM not allowed
- Principal is null, the processing of the SPNEGO Token failed
- SPNEGO Authorization header is not found under
- SPNEGO Authorization header
- User Agent header [ ] is not supported in the list of…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/4168a3401fec906f.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-spnego-webflow/src/main/java/org/apereo/cas/web/flow/SpnegoNegotiateCredentialsAction.java:70
* page on unsupported/configured browsers.
* <p>
* If this is set to false then the page is immediately closed after the
* unauthorized header is sent. This is ideal in environments that only
* want to use Windows Integrated Auth/SPNEGO and not forms auth.
*/
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);View on GitHub (pinned to e7288fc434)