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

  1. Configure cas.authn.spnego.supported-browsers with at least the default browser regex (e.g. MSIE.*, Firefox.*, AppleWebKit.*, etc.).
  2. Ensure clients attempting SPNEGO send a User-Agent header matching one of the supported browser patterns.
  3. Check any reverse proxy/load balancer is not stripping the User-Agent header before it reaches CAS.
  4. 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

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


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)