apereo/cas · warning

SPNEGO Authorization header

Error message

SPNEGO Authorization header [{}] does not begin with the prefix [{}]

What it means

After locating an Authorization header, SpnegoCredentialsAction requires it to start with the 'Negotiate' scheme prefix before Base64-decoding the SPNEGO token. If the header uses another scheme (Basic, Bearer, NTLM, wrong casing handled by the strict startsWith), it logs this warning and returns null. CAS thus refuses to treat the token as a Kerberos credential.

Solutions

  1. Make the client send 'Authorization: Negotiate <base64-token>' exactly, with the Negotiate prefix and a single space.
  2. Remove/fix any filter or proxy that injects Basic/Bearer Authorization headers before CAS SPNEGO handling.
  3. Check the client's authentication provider (e.g. force Kerberos over NTLM in the browser or HTTP client).
  4. Verify SpnegoConstants.NEGOTIATE casing matches what the client sends if a custom constant was configured.

Example fix

// before
request.setHeader("Authorization", "Bearer " + token);
// after
request.setHeader("Authorization", "Negotiate " + base64(spnegoToken));
Defensive patterns

Strategy: validation

Validate before calling

String authz = request.getHeader("Authorization");
if (authz == null || !authz.startsWith("Negotiate ")) {
    throw new IllegalArgumentException("Expected 'Authorization: Negotiate <token>'");
}

Type guard

static boolean isNegotiateScheme(String header) {
    return header != null && header.regionMatches(true, 0, "Negotiate ", 0, 10);
}

Prevention

When it happens

Trigger: Authorization header present but does not start with SpnegoConstants.NEGOTIATE ('Negotiate'), e.g. header is 'Basic ...', 'Bearer ...', 'NTLM ...', or a malformed 'Negotiate' spelling/casing mismatch within the first prefixLength chars, in constructCredentialsFromRequest.

Common situations: App behind CAS already sent Basic auth credentials that win the header race; client sends NTLM instead of Kerberos Negotiate; custom gateway rewriting the header; misconfigured client library sending Bearer tokens to the CAS login URL.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

            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;
    }

    @Override
    protected void onError(final RequestContext context) {
        setResponseHeader(context);
    }

    @Override
    protected void onSuccess(final RequestContext context) {
        setResponseHeader(context);
    }

    /**
     * Sets the response header based on the retrieved token.
     *
     * @param context the context

View on GitHub (pinned to e7288fc434)