v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: MEDIUM auth.java.jwt.unsigned-jwt

This JWT is created or parsed without a signature, so its contents are neither authenticated nor tamper-proof (CWE-347).

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

An unsecured ("alg=none") token can be forged by anyone. Changing the claims (e.g. the subject or roles) costs nothing because there is no signature to verify. This is a common AI-generated mistake: the "plaintext"/"unsecured" JWT API is reached for during prototyping and never swapped for a signed token.

Sign tokens and verify their signatures. With nimbus-jose-jwt use SignedJWT (and verify with a JWSVerifier); with jjwt build tokens via Jwts.builder()...signWith(key) and parse them with parseSignedClaims (or the legacy parseClaimsJws) instead of parseClaimsJwt / parsePlaintextJwt.

VULNERABLE
vulnerable.java
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import io.jsonwebtoken.Jwts;

class UnsignedJwt {

    // nimbus-jose-jwt: building an unsecured ("alg=none") JWT.
    PlainJWT nimbusUnsecured(JWTClaimsSet claims) {
        // ruleid: auth.java.jwt.unsigned-jwt
        return new PlainJWT(claims);
    }

    // jjwt: parsing an unsigned JWT with claims.
    void jjwtParseClaimsJwt(String token, byte[] key) {
        // ruleid: auth.java.jwt.unsigned-jwt
        Jwts.parser().build().parseClaimsJwt(token);
    }

    // jjwt: parsing an unsigned plaintext JWT.
    void jjwtParsePlaintextJwt(String token) {
        // ruleid: auth.java.jwt.unsigned-jwt
        Jwts.parser().build().parsePlaintextJwt(token);
    }
}
SAFE
safe.java
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.jsonwebtoken.Jwts;
import javax.crypto.SecretKey;

class SignedJwtOk {

    // nimbus-jose-jwt: a properly signed JWT.
    SignedJWT nimbusSigned(JWTClaimsSet claims, JWSSigner signer) throws Exception {
        // ok: auth.java.jwt.unsigned-jwt
        SignedJWT jwt = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claims);
        jwt.sign(signer);
        return jwt;
    }

    // jjwt: build a signed token.
    String jjwtSign(SecretKey key) {
        // ok: auth.java.jwt.unsigned-jwt
        return Jwts.builder().subject("alice").signWith(key).compact();
    }

    // jjwt: parse a signed token, verifying the signature.
    void jjwtParseSigned(String token, SecretKey key) {
        // ok: auth.java.jwt.unsigned-jwt
        Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.java.jwt.unsigned-jwt -- <reason>

References

https://connect2id.com/products/nimbus-jose-jwt/examples/unsecured-jwt ↗https://github.com/jwtk/jjwt#reading-a-jwt ↗https://cwe.mitre.org/data/definitions/347.html ↗