jwtk/jjwt · error · MalformedJwtException

The JWE header references key management algorithm '%s' but

Error message

The JWE header references key management algorithm '%s' but the compact JWE string is missing the required AAD authentication tag.

What it means

For JWEs with a key-management algorithm other than 'none', the compact string must include the final AAD authentication tag segment; jjwt throws MalformedJwtException when the digest (AEAD tag) component is missing. This check lives in the shared `!hasDigest` branch, producing the JWE-specific message format for TokenizedJwe tokens.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:426

            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
                // parser builder:
                supportedCrit = new LinkedHashSet<>(Collections.size(this.critical) + 1);
                supportedCrit.add(DefaultJwsHeader.B64.getId());
                supportedCrit.addAll(this.critical);
            }
            // assert any values per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11:
            for (String name : crit) {
                if (!header.containsKey(name)) {
                    String msg = String.format(CRIT_MISSING_MSG, name, name, header);

View on GitHub (pinned to fb71496164)

Solutions

  1. Re-obtain the complete JWE compact string including all five segments (protected header, encrypted key, IV, ciphertext, auth tag).
  2. Verify the token has exactly 4 dot separators before parsing as JWE.
  3. Fix the producer/serializer to always emit the authentication tag (use Jwts.builder().encryptWith(...)).
  4. Check for transport-layer truncation (loggers, headers, query strings cutting the token).

Example fix

// before: truncated JWE (missing auth tag)
String jwe = header + "." + key + "." + iv + "." + ciphertext;

// after: full 5-part JWE
String jwe = header + "." + key + "." + iv + "." + ciphertext + "." + tag;
// or generate: Jwts.builder().claims(c).encryptWith(k, alg, enc).compact();
Defensive patterns

Strategy: validation

Validate before calling

String[] parts = jwe.split("\\.", -1);
if (parts.length != 5 || parts[4].isEmpty()) throw new IllegalArgumentException("JWE must have 5 non-empty parts including the auth tag");

Try / catch

try { return parser.parse(jwe); }
catch (io.jsonwebtoken.MalformedJwtException e) { throw new InvalidTokenException("JWE missing authentication tag", e); }

Prevention

When it happens

Trigger: Calling parse()/parseSignedClaims() on a JWE compact string whose header declares a real key-management alg but which lacks the fifth (auth tag) segment — e.g. a truncated or wrongly serialized 4-segment string.

Common situations: Truncation during copy/paste, logging, or transport; custom serializers emitting only four JWE parts; confusion with JWS 3-segment format; misrouting a JWS string into a JWE parse path with mismatched header edits.

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/161da12878fccbeb. Report an issue: GitHub.