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: HIGH auth.py.fastapi.hardcoded-http-basic

A FastAPI HTTP Basic auth dependency compares the request's username or password against a hard-coded string literal.

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

The credential is baked into the source, so anyone who reads the code (or a leaked repo) has a working login, and the secret cannot be rotated without a redeploy (CWE-798). This is a common AI-generated mistake: FastAPI's own HTTP Basic example uses the literals "stanleyjobson" / "swordfish", and assistants copy that shape straight into production auth.

Compare against a secret loaded from the environment or a secret manager instead, e.g. secrets.compare_digest(credentials.password.encode("utf8"), os.environ["ADMIN_PASSWORD"].encode("utf8")), and prefer a real user store with per-user salted password hashes for anything beyond a single service account.

VULNERABLE
vulnerable.py
import secrets
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()
security = HTTPBasic()


def check_plain(credentials: HTTPBasicCredentials = Depends(security)):
    # ruleid: auth.py.fastapi.hardcoded-http-basic
    if credentials.username == "admin":
        # ruleid: auth.py.fastapi.hardcoded-http-basic
        if credentials.password == "s3cr3t":
            return credentials.username
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)


def check_compare_digest(
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
    # ruleid: auth.py.fastapi.hardcoded-http-basic
    is_user = secrets.compare_digest(
        credentials.username.encode("utf8"), b"stanleyjobson"
    )
    # ruleid: auth.py.fastapi.hardcoded-http-basic
    is_pass = secrets.compare_digest(
        credentials.password.encode("utf8"), b"swordfish"
    )
    if not (is_user and is_pass):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return credentials.username


async def check_async(credentials: HTTPBasicCredentials = Depends(security)):
    # ruleid: auth.py.fastapi.hardcoded-http-basic
    if secrets.compare_digest(credentials.password, "hunter2"):
        return credentials.username
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
SAFE
safe.py
import os
import secrets
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()
security = HTTPBasic()


def check_env(credentials: HTTPBasicCredentials = Depends(security)):
    # Compared against secrets from the environment — not hard-coded.
    # ok: auth.py.fastapi.hardcoded-http-basic
    is_user = secrets.compare_digest(
        credentials.username.encode("utf8"),
        os.environ["ADMIN_USER"].encode("utf8"),
    )
    # ok: auth.py.fastapi.hardcoded-http-basic
    is_pass = secrets.compare_digest(
        credentials.password.encode("utf8"),
        os.environ["ADMIN_PASSWORD"].encode("utf8"),
    )
    if not (is_user and is_pass):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return credentials.username


def check_userstore(
    credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
    user = lookup_user(credentials.username)
    # ok: auth.py.fastapi.hardcoded-http-basic
    if user and verify_password(credentials.password, user.password_hash):
        return user
    raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)


def unrelated_login(username: str, password: str):
    # Not a FastAPI HTTPBasicCredentials dependency — must not fire.
    # ok: auth.py.fastapi.hardcoded-http-basic
    if password == "letmein":
        return True
    return False

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.fastapi.hardcoded-http-basic -- <reason>

References

https://fastapi.tiangolo.com/advanced/security/http-basic-auth/ ↗https://cwe.mitre.org/data/definitions/798.html ↗