jwtk/jjwt · error · UnsupportedJwtException

Unsecured JWSs (those with an alg header value of 'none') ar

Error message

Unsecured JWSs (those with an alg header value of 'none') are disallowed by default as mandated by https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6. If you wish to allow them to be parsed, call the JwtParserBuilder.unsecured() method, but please read the security considerations covered in that method's JavaDoc before doing so. Header: ${header}

What it means

Unsecured JWSs (alg='none') are rejected by default as mandated by RFC 7518 Section 3.6. jjwt refuses to parse them unless the application explicitly opts in by calling JwtParserBuilder.unsecured(); when alg is 'none' and the parser is not in unsecured mode, an UnsupportedJwtException carrying the offending header is thrown.

Source

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

        //      Compact Serialization and is the approach taken by CMS [RFC6211].)
        //
        final String alg = Strings.clean(header.getAlgorithm());
        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();

View on GitHub (pinned to fb71496164)

Solutions

  1. If you intentionally need unsecured tokens, build the parser with Jwts.parser().unsecured() ... (read the method's security JavaDoc first).
  2. Prefer securing tokens: re-issue them signed with a real algorithm (e.g. HS256/RS256) and parse with verifyWith(key).
  3. If unsecured tokens are unexpected, treat this as an upstream issuer problem and reject the token.
  4. For local/dev testing only, enable unsecured parsing in a non-production parser instance.

Example fix

// before
Jws<Claims> jws = Jwts.parser().build().parseSignedClaims(noneToken);

// after (only if truly intended)
JwtParser parser = Jwts.parser().unsecured().build();
Jwt<Header, Claims> jwt = parser.parse(noneToken);
Defensive patterns

Strategy: validation

Validate before calling

String headerJson = new String(java.util.Base64.getUrlDecoder().decode(token.split("\\.")[0]), java.nio.charset.StandardCharsets.UTF_8);
boolean unsecured = headerJson.contains("alg\":\"none\"");
JwtParser p = unsecured ? Jwts.parser().unsecured().build() : Jwts.parser().verifyWith(key).build();

Try / catch

try { return parser.parse(token); }
catch (io.jsonwebtoken.UnsupportedJwtException e) { throw new InvalidTokenException("Unsecured JWS rejected", e); }

Prevention

When it happens

Trigger: Calling parse(), parseSignedClaims(), or parseSignedContent() on a 3-segment JWS whose header is "alg":"none" while the parser was built without JwtParserBuilder.unsecured().

Common situations: Parsing tokens minted by legacy systems or dev environments that use unsigned JWTs; testing with hand-made none-alg tokens; migrating from libraries that allowed none by default; misconfiguration where the parser was not built with .unsecured().

Related errors


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