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.rust.jwt.no-issuer-validation

A JWT is decoded with a jsonwebtoken Validation that never sets the expected issuer.

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

Why this matters

Because the issuer is not pinned, decode accepts a token minted by ANY issuer: the jsonwebtoken crate does not validate the iss claim unless you opt in, so a token signed by an attacker-controlled or otherwise untrusted issuer passes validation as long as the signature checks out. For OAuth/OIDC this lets a token from the wrong authorization server be replayed against this API.

Pin the issuer before decoding, e.g. validation.set_issuer(&["https://issuer.example.com"]) (or set validation.iss), so only tokens whose iss claim matches your trusted authorization server are accepted.

VULNERABLE
vulnerable.rs
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Claims {
    sub: String,
    iss: String,
    exp: usize,
}

fn decode_new(token: &str, key: &DecodingKey) -> Claims {
    // ruleid: auth.rust.jwt.no-issuer-validation
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_audience(&["my-api"]);
    decode::<Claims>(token, key, &validation).unwrap().claims
}

fn decode_default(token: &str, key: &DecodingKey) -> Claims {
    // ruleid: auth.rust.jwt.no-issuer-validation
    let mut validation = Validation::default();
    decode::<Claims>(token, key, &validation).unwrap().claims
}
SAFE
safe.rs
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Claims {
    sub: String,
    iss: String,
    exp: usize,
}

// ok: auth.rust.jwt.no-issuer-validation -- issuer pinned via set_issuer
fn decode_with_issuer(token: &str, key: &DecodingKey) -> Claims {
    let mut validation = Validation::new(Algorithm::HS256);
    validation.set_issuer(&["https://issuer.example.com"]);
    decode::<Claims>(token, key, &validation).unwrap().claims
}

// ok: auth.rust.jwt.no-issuer-validation -- issuer pinned via the iss field
fn decode_with_iss_field(token: &str, key: &DecodingKey) -> Claims {
    let mut validation = Validation::new(Algorithm::HS256);
    validation.iss = Some(["https://issuer.example.com".to_string()].into_iter().collect());
    decode::<Claims>(token, key, &validation).unwrap().claims
}

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.rust.jwt.no-issuer-validation -- <reason>

References

https://docs.rs/jsonwebtoken/latest/jsonwebtoken/struct.Validation.html#method.set_issuer ↗https://cwe.mitre.org/data/definitions/345.html ↗https://cwe.mitre.org/data/definitions/287.html ↗