jwtk/jjwt · error · UnsupportedJwtException

Unexpected unsecured Claims JWT.

Error message

Unexpected unsecured Claims JWT.

What it means

SupportedJwtVisitor.onUnsecuredClaims is a default visitor callback invoked when the parser encounters an unsecured JWT (alg=none) whose payload is a Claims JSON object. The base class intentionally throws UnsupportedJwtException because plain, unsigned JWTs are unsafe (their contents can be modified by anyone without detection). Applications must override this method to explicitly opt in to handling unsecured Claims JWTs.

Source

Thrown at api/src/main/java/io/jsonwebtoken/SupportedJwtVisitor.java:89

     * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary.
     */
    public T onUnsecuredContent(Jwt<Header, byte[]> jwt) throws UnsupportedJwtException {
        throw new UnsupportedJwtException("Unexpected unsecured content JWT.");
    }

    /**
     * Handles an encountered unsecured Claims JWT - one that is not cryptographically signed nor
     * encrypted, and has a {@link Claims} payload.
     *
     * <p>The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that
     * subclasses will override this method if the application needs to support this type of JWT.</p>
     *
     * @param jwt the parsed unsecured content JWT
     * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
     * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary.
     */
    public T onUnsecuredClaims(Jwt<Header, Claims> jwt) {
        throw new UnsupportedJwtException("Unexpected unsecured Claims JWT.");
    }

    /**
     * Handles an encountered JSON Web Token (aka 'JWS') message that has been cryptographically verified/authenticated
     * by delegating to either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} depending on the payload
     * type.
     *
     * @param jws the parsed verified/authenticated JWS.
     * @return the value returned by either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)}
     * depending on the payload type.
     * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either
     *                                 delegate method throws the same.
     */
    @SuppressWarnings("unchecked")
    @Override
    public T visit(Jws<?> jws) {
        Assert.notNull(jws, "JWS cannot be null.");
        Object payload = jws.getPayload();

View on GitHub (pinned to fb71496164)

Solutions

  1. Sign the JWT on the issuing side (e.g. jwsBuilder via Jwts.builder().signWith(key)) and verify it on parse, eliminating unsecured tokens entirely.
  2. If unsecured Claims JWTs are genuinely expected, subclass SupportedJwtVisitor and override onUnsecuredClaims to return a value instead of throwing.
  3. If unsecured tokens should never appear, treat the exception as a security signal: reject the token and log/audit the source.
  4. Use parseClaimsJwt only for tokens you know are unsecured; otherwise use parseClaimsJws with the correct verification key.

Example fix

// before
T result = visitor.onUnsecuredClaims(jwt); // throws UnsupportedJwtException
// after
public class MyVisitor extends SupportedJwtVisitor<MyType> {
    @Override
    public MyType onUnsecuredClaims(Jwt<Header, Claims> jwt) {
        if (isTrustedUnsecuredIssuer(jwt.getHeader())) {
            return processClaims(jwt.getBody());
        }
        throw new UnsupportedJwtException("Unsecured JWTs not allowed");
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Inspect the token before parsing: an unsecured JWT has a trailing dot and alg=none header
String[] parts = compactJwt.split("\\.", -1);
boolean unsecured = parts.length == 3 && parts[2].isEmpty();
if (unsecured && !allowUnsecuredTokens) {
    throw new IllegalArgumentException("Unsecured JWTs are not accepted");
}

Type guard

boolean isUnsecuredJwt(String token) {
    String[] p = token.split("\\.", -1);
    return p.length == 3 && p[2].isEmpty() && new String(Base64.getUrlDecoder().decode(p[0])).contains("\"none\"");
}

Try / catch

try {
    result = Jwts.parser().build().parseClaimsJwt(token);
} catch (UnsupportedJwtException e) {
    log.warn("Rejected unsecured Claims JWT", e);
    throw new SecurityException("Unsecured JWTs are not allowed here", e);
}

Prevention

When it happens

Trigger: Parsing a compact 'header.payload.' token whose header declares alg=none (or an unsecured JWS) with a Claims payload, using a JwtParser whose visitor does not override onUnsecuredClaims. E.g. Jwts.parser().build().parse(...)/parseClaimsJwt on a token created with Jwts.builder()...compact() without signing (or signWith with no key / alg none).

Common situations: Migrating code that previously accepted unsigned tokens; tokens received from a legacy or third-party issuer that emits alg=none tokens; a developer forgot to configure a signing key so the produced token was unsecured; security-hardened parsers rejecting unsigned tokens by design.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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