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
- Make the client send 'Authorization: Negotiate <base64-token>' exactly, with the Negotiate prefix and a single space.
- Remove/fix any filter or proxy that injects Basic/Bearer Authorization headers before CAS SPNEGO handling.
- Check the client's authentication provider (e.g. force Kerberos over NTLM in the browser or HTTP client).
- 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
- Send exact 'Negotiate <base64>' scheme in clients
- Audit filters/proxies that rewrite Authorization
- Prefer Kerberos over NTLM in client config
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
- SPNEGO Authorization header is not found under
- NTLM not allowed
- Principal is null, the processing of the SPNEGO Token failed
- User Agent header [ ] is empty, or no browsers are supported
- No value could be retrieved from the header
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 contextView on GitHub (pinned to e7288fc434)