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 the

View on GitHub (pinned to fb71496164)

Solutions

  1. Re-issue the token correctly: either sign it with a real algorithm, or use alg=none with an empty trailing segment (header.payload.).
  2. Remove the signature segment if the token is meant to be unsecured (and ensure the parser permits unsecured tokens).
  3. Check the producing library/configuration for always appending signatures regardless of algorithm.
  4. 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

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


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/dabb5bf9e5914c67. Report an issue: GitHub.