spring-projects/spring-security · error · BadJwtException

An error occurred while attempting to decode the Jwt…

Error message

An error occurred while attempting to decode the Jwt: Malformed token

What it means

NimbusJwtDecoder.parse wraps JWTParser.parse failures: when Nimbus throws ParseException the token is structurally invalid, and the decoder throws BadJwtException 'An error occurred while attempting to decode the Jwt: Malformed token'.

Solutions

  1. Confirm the token is actually a JWT (three dot-separated Base64URL segments) — log/inspect the raw Authorization header server-side.
  2. Check for transport corruption: strip whitespace, avoid re-encoding, ensure the full token is forwarded (long tokens can be cut by proxies/gateways).
  3. If the token is opaque, use an introspection decoder (NimbusOpaqueTokenIntrospector / OpaqueTokenIntrospector) instead of NimbusJwtDecoder.
  4. Catch BadJwtException and return 401 so the client re-authenticates with a fresh token.

Example fix

// before
String token = request.getHeader("Authorization").replace("Bearer ", "").trim();
// after
String auth = request.getHeader("Authorization");
if (auth != null && auth.startsWith("Bearer ")) {
    String token = auth.substring(7).trim(); // validate non-empty, 3 segments
}
Defensive patterns

Strategy: try-catch

Validate before calling

String token = authorizationHeader != null && authorizationHeader.startsWith("Bearer ")
    ? authorizationHeader.substring(7).trim() : null;
boolean plausible = token != null && token.chars().filter(c -> c == '.').count() == 2
    && !token.contains(" ") && !token.contains("\n");

Type guard

boolean looksLikeJwt(String token) {
    return token != null && token.split("\\.", -1).length == 3
        && token.matches("[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]*");
}

Try / catch

try { return jwtDecoder.decode(token); }
catch (BadJwtException e) {
    // malformed token: respond 401 WWW-Authenticate: Bearer error="invalid_token"
}

Prevention

When it happens

Trigger: decode() → parse(token) with a string that is not parseable as a JWT: missing segments, invalid Base64URL, oversized/malformed JSON header or payload, wrong compact serialization.

Common situations: Sending an opaque token or OAuth2 access token that is not a JWT to a JWT decoder; truncating the Authorization header value; whitespace/newlines inside the token; double-URL-encoding the bearer token in transit; proxy mangling the header.

Understand the failure class

Related errors


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

Appendix: source

Thrown at oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java:156

	@Override
	public Jwt decode(String token) throws JwtException {
		JWT jwt = parse(token);
		if (jwt instanceof PlainJWT) {
			this.logger.trace("Failed to decode unsigned token");
			throw new BadJwtException("Unsupported algorithm of " + jwt.getHeader().getAlgorithm());
		}
		Jwt createdJwt = createJwt(token, jwt);
		return validateJwt(createdJwt);
	}

	private JWT parse(String token) {
		try {
			return JWTParser.parse(token);
		}
		catch (Exception ex) {
			this.logger.trace("Failed to parse token", ex);
			if (ex instanceof ParseException) {
				throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, "Malformed token"), ex);
			}
			throw new BadJwtException(String.format(DECODING_ERROR_MESSAGE_TEMPLATE, ex.getMessage()), ex);
		}
	}

	private Jwt createJwt(String token, JWT parsedJwt) {
		try {
			// Verify the signature
			JWTClaimsSet jwtClaimsSet = this.jwtProcessor.process(parsedJwt, null);
			Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
			Map<String, Object> claims = this.claimSetConverter.convert(jwtClaimsSet.getClaims());
			// @formatter:off
			return Jwt.withTokenValue(token)
					.headers((h) -> h.putAll(headers))
					.claims((c) -> c.putAll(claims))
					.build();
			// @formatter:on
		}

View on GitHub (pinned to 96852e8860)