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

OAuth authorization request sends a hardcoded, constant state value.

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.

Generate state fresh per request from a CSPRNG (secrets.token_urlsafe(32)), persist it in the session, and verify it when the provider redirects back.

VULNERABLE
vulnerable.py
"""OAuth authorize requests with a hardcoded, constant state value."""

import requests

AUTHORIZE_URL = "https://idp.example.com/authorize"


def build_params():
    # ruleid: auth.py.oauth.static-state
    params = {
        "response_type": "code",
        "client_id": "my-client",
        "redirect_uri": "https://app.example.com/callback",
        "state": "static-state-123",
    }
    return requests.get(AUTHORIZE_URL, params=params)


def inline_authorize_url():
    # ruleid: auth.py.oauth.static-state
    return "https://idp.example.com/authorize?response_type=code&client_id=abc&state=fixed123"
SAFE
safe.py
"""OAuth authorize requests with a per-request CSPRNG state."""

import secrets

import requests

AUTHORIZE_URL = "https://idp.example.com/authorize"


def build_params():
    # ok: auth.py.oauth.static-state -- state generated fresh per request
    state = secrets.token_urlsafe(32)
    params = {
        "response_type": "code",
        "client_id": "my-client",
        "state": state,
    }
    return requests.get(AUTHORIZE_URL, params=params), state


def inline_authorize_url(state: str):
    # ok: auth.py.oauth.static-state -- dynamic state interpolated via f-string
    return f"https://idp.example.com/authorize?response_type=code&client_id=abc&state={state}"


def unrelated_state_field():
    # ok: auth.py.oauth.static-state -- a literal "state" with no response_type is not an authorize request
    return {"state": "CA", "city": "San Francisco"}

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

References

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