jwtk/jjwt · error · MalformedJwtException
The JWS header references signature algorithm 'none' yet the
Error message
The JWS header references signature algorithm 'none' yet the compact JWS string contains a signature. This is not permitted per https://tools.ietf.org/html/rfc7518#section-3.6.
What it means
A JWS whose header declares alg='none' must carry no signature (RFC 7518 Section 3.6). jjwt throws MalformedJwtException when the compact string declares 'none' but still contains a non-empty signature (digest) component, because the token contradicts its own header.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:417
if (!Strings.hasText(alg)) {
String msg = tokenized instanceof TokenizedJwe ? MISSING_JWE_ALG_MSG : MISSING_JWS_ALG_MSG;
throw new MalformedJwtException(msg);
}
final boolean unsecured = Jwts.SIG.NONE.getId().equalsIgnoreCase(alg);
final CharSequence base64UrlDigest = tokenized.getDigest();
final boolean hasDigest = Strings.hasText(base64UrlDigest);
if (unsecured) {
if (tokenized instanceof TokenizedJwe) {
throw new MalformedJwtException(JWE_NONE_MSG);
}
// Unsecured JWTs are disabled by default per the RFC:
if (!this.unsecured) {
String msg = UNSECURED_DISABLED_MSG_PREFIX + header;
throw new UnsupportedJwtException(msg);
}
if (hasDigest) {
throw new MalformedJwtException(JWS_NONE_SIG_MISMATCH_MSG);
}
if (header.containsKey(DefaultProtectedHeader.CRIT.getId())) {
String msg = String.format(CRIT_UNSECURED_MSG, header);
throw new MalformedJwtException(msg);
}
} else if (!hasDigest) { // something other than 'none'. Must have a digest component:
String fmt = tokenized instanceof TokenizedJwe ? MISSING_JWE_DIGEST_MSG_FMT : MISSING_JWS_DIGEST_MSG_FMT;
String msg = String.format(fmt, alg);
throw new MalformedJwtException(msg);
}
// ----- crit assertions -----
if (header instanceof ProtectedHeader) {
Set<String> crit = Collections.nullSafe(((ProtectedHeader) header).getCritical());
Set<String> supportedCrit = this.critical;
String b64Id = DefaultJwsHeader.B64.getId();
if (!unencodedPayload.isEmpty() && !this.critical.contains(b64Id)) {
// The application developer explicitly indicates they're using a B64 payload, so
// ensure that the B64 crit header is supported, even if they forgot to configure it on theView on GitHub (pinned to fb71496164)
Solutions
- Re-issue the token correctly: either sign it with a real algorithm, or use alg=none with an empty trailing segment (header.payload.).
- Remove the signature segment if the token is meant to be unsecured (and ensure the parser permits unsecured tokens).
- Check the producing library/configuration for always appending signatures regardless of algorithm.
- Treat mismatched none-alg-with-signature tokens as potentially tampered and reject them.
Example fix
// before: alg=none but signature present String bad = b64(headerNone) + "." + b64(payload) + "." + b64(sig); // after: unsecured JWS has empty signature segment String good = b64(headerNone) + "." + b64(payload) + ".";
Defensive patterns
Strategy: validation
Validate before calling
String[] parts = token.split("\\.", -1);
String headerJson = new String(java.util.Base64.getUrlDecoder().decode(parts[0]), java.nio.charset.StandardCharsets.UTF_8);
if (headerJson.contains("alg\":\"none\"") && parts.length > 2 && !parts[2].isEmpty()) throw new IllegalArgumentException("alg=none token must not carry a signature"); Try / catch
try { return parser.parse(token); }
catch (io.jsonwebtoken.MalformedJwtException e) { throw new InvalidTokenException("none-alg token with signature", e); } Prevention
- Unsecured JWS must end with an empty signature segment
- Never change a token's alg header without recomputing/removing the signature
- Use a library builder instead of string concatenation
- Treat inconsistent none+signature tokens as potentially tampered
When it happens
Trigger: Parsing a JWS with alg='none' in the header but a third compact segment (signature) present — i.e. unsecured=true enabled on the parser and hasDigest is true.
Common situations: Tokens assembled by concatenating an unsigned header/payload with a leftover signature segment; buggy custom serializers that always append a signature; corruption or tampering where the alg was changed to none without removing the signature.
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
- JWS header does not contain a required 'alg' (Algorithm) hea
- Unsecured JWSs (those with an alg header value of 'none') ar
- Unsecured JWSs (those with an alg header value of 'none') ma
- Protected Header crit set references header name '%s', but t
- Unexpected content JWS.
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/dabb5bf9e5914c67.
Report an issue: GitHub.