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 →
MEDIUM AI PREVALENCE: MEDIUM auth.java.jwt.no-claims-validation

This JWT is verified for signature but its intended-recipient claims are never asserted: the Auth0 JWT.require(alg)...build() verifier pins no withIssuer(...)/withAudience(...), or the jjwt parser sets a signature key but pins no requireIssuer(...)/requireAudience(...).

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

Why this matters

A valid signature only proves the token was minted by whoever holds the key, not that it was issued by the expected authority or meant for THIS service. A token your provider issued for a different audience (or one minted by any party that shares the key) will still verify, enabling token replay across services (CWE-345, Insufficient Verification of Data Authenticity). This is a common AI-generated mistake: the sample verifies the signature and stops there.

Pin the token's recipient. With Auth0 java-jwt chain .withIssuer("https://your-idp") and .withAudience("your-api") before .build(); with jjwt chain .requireIssuer(...) and .requireAudience(...) before .build().

VULNERABLE
vulnerable.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.jsonwebtoken.Jwts;
import java.security.PublicKey;
import javax.crypto.SecretKey;

class TokenValidator {

    // Auth0 java-jwt: signature verified, but issuer/audience never pinned.
    DecodedJWT auth0Bare(String token, Algorithm alg) {
        // ruleid: auth.java.jwt.no-claims-validation
        return JWT.require(alg).build().verify(token);
    }

    // Auth0 java-jwt: an intermediate builder call, still no recipient pinned.
    DecodedJWT auth0Leeway(String token, Algorithm alg) {
        // ruleid: auth.java.jwt.no-claims-validation
        return JWT.require(alg)
                .acceptLeeway(60)
                .build()
                .verify(token);
    }

    // jjwt: signature key set, but requireIssuer/requireAudience missing.
    void jjwtParserBuilder(String token, SecretKey key) {
        // ruleid: auth.java.jwt.no-claims-validation
        Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
    }

    // jjwt: verifyWith key set, but no recipient claims required.
    void jjwtParser(String token, PublicKey key) {
        // ruleid: auth.java.jwt.no-claims-validation
        Jwts.parser().verifyWith(key).build().parseSignedClaims(token);
    }
}
SAFE
safe.java
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.jsonwebtoken.Jwts;
import java.security.PublicKey;
import javax.crypto.SecretKey;

class TokenValidator {

    // Auth0 java-jwt: issuer AND audience pinned before build().
    DecodedJWT auth0Pinned(String token, Algorithm alg) {
        // ok: auth.java.jwt.no-claims-validation
        return JWT.require(alg)
                .withIssuer("https://idp.example.com")
                .withAudience("https://api.example.com")
                .acceptLeeway(60)
                .build()
                .verify(token);
    }

    // jjwt: recipient claims required alongside the signature key.
    void jjwtPinned(String token, SecretKey key) {
        // ok: auth.java.jwt.no-claims-validation
        Jwts.parserBuilder()
                .setSigningKey(key)
                .requireIssuer("https://idp.example.com")
                .requireAudience("https://api.example.com")
                .build()
                .parseClaimsJws(token);
    }

    // jjwt: verifyWith + recipient assertions in a single chain.
    void jjwtParserPinned(String token, PublicKey key) {
        // ok: auth.java.jwt.no-claims-validation
        Jwts.parser()
                .verifyWith(key)
                .requireIssuer("https://idp.example.com")
                .requireAudience("https://api.example.com")
                .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.no-claims-validation -- <reason>

References

https://datatracker.ietf.org/doc/html/rfc8725#section-3.8 ↗https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3 ↗https://cwe.mitre.org/data/definitions/345.html ↗