apereo/cas · warning

SPNEGO Authorization header is not found under

Error message

SPNEGO Authorization header is not found under [{}]

What it means

CAS's SPNEGO webflow action tries to build SPNEGO credentials from the HTTP Authorization header. When the header is absent or blank, it logs this warning and returns null so the webflow falls back (typically to prompting the client for Negotiate). It is a diagnostic for the client not having sent a Kerberos/SPNEGO token at all.

Solutions

  1. Ensure the CAS server hostname is added to the browser's SPNEGO trusted sites / intranet zone so it sends the Negotiate token.
  2. Verify the request actually reaches CAS with the header intact (check reverse proxy/ingress rules that strip Authorization).
  3. Test with curl --negotiate -u : <cas-login-url> to confirm header transmission.
  4. Confirm the SPNEGO webflow is configured so clients are redirected to negotiate instead of expecting a header on first hit.

Example fix

// before
curl https://cas.example.org/login
// after
curl --negotiate -u : https://cas.example.org/login
Defensive patterns

Strategy: validation

Validate before calling

String authz = request.getHeader("Authorization");
if (authz == null || authz.isBlank()) {
    // skip SPNEGO / fall back to form login before invoking CAS action
}

Type guard

static boolean hasNegotiateHeader(HttpServletRequest r) {
    String h = r.getHeader("Authorization");
    return h != null && h.regionMatches(true, 0, "Negotiate ", 0, "Negotiate ".length());
}

Prevention

When it happens

Trigger: Browser or HTTP client hits /login with SPNEGO enabled but sends no Authorization header (or sends a blank one); getHeader('Authorization') and its lowercase variant both return null/blank in constructCredentialsFromRequest.

Common situations: Direct navigation (not via an intranet SSO-aware app) so the browser never pre-authenticates; browser SPNEGO/Integrated Authentication disabled or the CAS host not in the trusted-URI/intranet-sites list; reverse proxy stripping the Authorization header; non-browser clients (curl, scripts) calling the endpoint.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/dce52e97722199f0. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-spnego-webflow/src/main/java/org/apereo/cas/web/flow/SpnegoCredentialsAction.java:62

    public SpnegoCredentialsAction(final CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver,
                                   final CasWebflowEventResolver serviceTicketRequestWebflowEventResolver,
                                   final AdaptiveAuthenticationPolicy adaptiveAuthenticationPolicy,
                                   final boolean send401OnAuthenticationFailure) {
        super(initialAuthenticationAttemptWebflowEventResolver, serviceTicketRequestWebflowEventResolver, adaptiveAuthenticationPolicy);
        this.send401OnAuthenticationFailure = send401OnAuthenticationFailure;
    }

    @Override
    protected Credential constructCredentialsFromRequest(final RequestContext context) {
        val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);

        LOGGER.debug("Available request headers are [{}]", Collections.list(request.getHeaderNames()));
        val authorizationHeader = StringUtils.defaultIfBlank(
            request.getHeader(HttpHeaders.AUTHORIZATION),
            request.getHeader(HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ENGLISH)));
        LOGGER.debug("SPNEGO Authorization header located as [{}]", authorizationHeader);
        if (StringUtils.isBlank(authorizationHeader)) {
            LOGGER.warn("SPNEGO Authorization header is not found under [{}]", HttpHeaders.AUTHORIZATION);
            return null;
        }

        val authzHeaderLength = authorizationHeader.length();
        val prefixLength = SpnegoConstants.NEGOTIATE.length();
        if (authzHeaderLength > prefixLength && authorizationHeader.startsWith(SpnegoConstants.NEGOTIATE)) {
            LOGGER.debug("SPNEGO Authorization header found with [{}] bytes", authzHeaderLength - prefixLength);
            val base64 = authorizationHeader.substring(prefixLength);
            val token = EncodingUtils.decodeBase64(base64);
            val tokenString = new String(token, Charset.defaultCharset());
            LOGGER.debug("Obtained token: [{}]. Creating credential...", tokenString);
            return new SpnegoCredential(token);
        }
        LOGGER.warn("SPNEGO Authorization header [{}] does not begin with the prefix [{}]",
            authorizationHeader, SpnegoConstants.NEGOTIATE);
        return null;
    }

View on GitHub (pinned to e7288fc434)