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.oauth.static-state

OAuth authorization request is built with a hardcoded, constant state value (CsrfToken::new("literal")).

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 static state provides ZERO CSRF protection: the whole point is an unguessable, per-request value that you store and then compare on the callback. A literal that ships in your source is known to everyone and identical on every request, so an attacker can forge a matching callback.

Use CsrfToken::new_random (the oauth2 crate's CSPRNG-backed generator), pass it to authorize_url, persist it in the session, and verify it when the provider redirects back.

VULNERABLE
vulnerable.rs
use oauth2::basic::BasicClient;
use oauth2::CsrfToken;

// Hardcoded state via .to_string() — constant on every request.
fn authorize_static(client: &BasicClient) {
    // ruleid: auth.rust.oauth.static-state
    let _ = client.authorize_url(|| CsrfToken::new("static-state-123".to_string()));
}

// Hardcoded state via String::from.
fn authorize_static_from(client: &BasicClient) {
    // ruleid: auth.rust.oauth.static-state
    let _ = client.authorize_url(|| CsrfToken::new(String::from("constant")));
}

// Hardcoded state via .into().
fn authorize_static_into(client: &BasicClient) {
    // ruleid: auth.rust.oauth.static-state
    let _ = client.authorize_url(|| CsrfToken::new("fixed".into()));
}

fn main() {}
SAFE
safe.rs
use oauth2::basic::BasicClient;
use oauth2::CsrfToken;

// Safe: a fresh per-request CSPRNG-backed state.
fn authorize_random(client: &BasicClient) {
    // ok: auth.rust.oauth.static-state
    let _ = client.authorize_url(CsrfToken::new_random);
}

// helper that derives a state value at runtime
fn derive_state(session_id: &str) -> String {
    format!("{}-{}", session_id, "nonce")
}

// Safe trap: the argument is a function call (with its own string literal
// inside), not a literal state — the precise literal-only patterns must not
// match it.
fn authorize_derived(client: &BasicClient, session_id: &str) {
    let state = derive_state(session_id);
    // ok: auth.rust.oauth.static-state
    let _ = client.authorize_url(|| CsrfToken::new(state.clone()));
}

fn main() {}

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.oauth.static-state -- <reason>

References

https://datatracker.ietf.org/doc/html/rfc6749#section-10.12 ↗https://cwe.mitre.org/data/definitions/330.html ↗