spring-projects/spring-security · error · BadCredentialsException

Empty basic authentication token

Error message

Empty basic authentication token

What it means

BasicAuthenticationConverter.convert parses the Authorization header and requires content after the 'Basic' scheme. If the header is exactly 'Basic' (case-insensitive, possibly with trailing whitespace trimmed) with no Base64 payload, it throws BadCredentialsException('Empty basic authentication token'). The scheme is present but the credentials are missing entirely.

Solutions

  1. Fix the client to send 'Basic ' + Base64(username:password)
  2. Verify proxies/gateways are not stripping the credential portion of the header
  3. Ensure client credentials (env vars, config) are actually populated and not empty strings
  4. Optionally pre-check the header client-side before sending

Example fix

// before
request.header("Authorization", "Basic"); // empty credentials
// after
String creds = Base64.getEncoder().encodeToString((user + ":" + pass).getBytes(UTF_8));
request.header("Authorization", "Basic " + creds);
Defensive patterns

Strategy: try-catch

Validate before calling

String h = request.getHeader("Authorization");
if (h == null || h.trim().equalsIgnoreCase("Basic")) {
    response.setHeader("WWW-Authenticate", "Basic realm=\"app\"");
    response.sendError(401, "Missing basic credentials");
    return;
}

Type guard

boolean hasBasicCredentials(String header) {
    return header != null && header.trim().toLowerCase().startsWith("basic ")
        && header.trim().length() > 6;
}

Try / catch

try {
    Authentication a = converter.convert(request);
} catch (BadCredentialsException e) {
    response.setHeader("WWW-Authenticate", "Basic realm=\"app\"");
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}

Prevention

When it happens

Trigger: An Authorization: Basic header with no value is sent — e.g. a client sends the scheme but no credentials, a gateway strips the Base64 part, or curl/testing tools send an incomplete header.

Common situations: HTTP clients sending an empty credentials string; reverse proxies or API gateways rewriting Authorization headers; misconfigured clients where username/password variables are empty; manual curl testing with 'Authorization: Basic' only.

Understand the failure class

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/1e49e070f636a3ad. Report an issue: GitHub.

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java:89

	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	@Override
	public @Nullable UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
		String header = request.getHeader(HttpHeaders.AUTHORIZATION);
		if (header == null) {
			return null;
		}
		header = header.trim();
		if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) {
			return null;
		}
		if (header.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) {
			throw new BadCredentialsException("Empty basic authentication token");
		}
		byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
		byte[] decoded = decode(base64Token);
		String token = new String(decoded, getCredentialsCharset(request));
		int delim = token.indexOf(":");
		if (delim == -1) {
			throw new BadCredentialsException("Invalid basic authentication token");
		}
		UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken
			.unauthenticated(token.substring(0, delim), token.substring(delim + 1));
		result.setDetails(this.authenticationDetailsSource.buildDetails(request));
		return result;
	}

	private byte[] decode(byte[] base64Token) {
		try {
			return Base64.getDecoder().decode(base64Token);
		}

View on GitHub (pinned to 96852e8860)