{"record":{"id":"b794d7a3932b3334","repo":"tiangolo/fastapi","slug":"incorrect-username-or-password-b794d7","errorCode":null,"errorMessage":"Incorrect username or password","messagePattern":"Incorrect username or password","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"docs_src/security/tutorial007_an_py310.py","lineNumber":26,"sourceCode":"\nsecurity = HTTPBasic()\n\n\ndef get_current_username(\n    credentials: Annotated[HTTPBasicCredentials, Depends(security)],\n):\n    current_username_bytes = credentials.username.encode(\"utf8\")\n    correct_username_bytes = b\"stanleyjobson\"\n    is_correct_username = secrets.compare_digest(\n        current_username_bytes, correct_username_bytes\n    )\n    current_password_bytes = credentials.password.encode(\"utf8\")\n    correct_password_bytes = b\"swordfish\"\n    is_correct_password = secrets.compare_digest(\n        current_password_bytes, correct_password_bytes\n    )\n    if not (is_correct_username and is_correct_password):\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Incorrect username or password\",\n            headers={\"WWW-Authenticate\": \"Basic\"},\n        )\n    return credentials.username\n\n\n@app.get(\"/users/me\")\ndef read_current_user(username: Annotated[str, Depends(get_current_username)]):\n    return {\"username\": username}\n","sourceCodeStart":8,"sourceCodeEnd":37,"githubUrl":"https://github.com/tiangolo/fastapi/blob/3e8d1526d83a90aaf7d6eb6dc682bf150f180b25/docs_src/security/tutorial007_an_py310.py#L8-L37","documentation":"HTTP Basic auth tutorial. get_current_username uses secrets.compare_digest (constant-time) on both the username ('stanleyjobson') and the password ('swordfish'), combining them in a single AND so that neither check short-circuits — preventing timing-based credential discovery. If either fails it raises HTTP 401 with WWW-Authenticate: Basic, which makes browsers show the native login prompt. Credentials are hardcoded in source for the demo.","triggerScenarios":"GET /users/me with no Authorization header, or with Basic credentials whose base64-decoded username is not 'stanleyjobson' or whose password is not 'swordfish'. A browser prompted by WWW-Authenticate: Basic will resubmit whatever the user types.","commonSituations":"Typo in credentials; browser cached old Basic creds and keeps replaying them; mis-base64-encoded test header; demo creds rotated but the hardcoded bytes were not updated.","solutions":["Send Authorization: Basic <base64('stanleyjobson:swordfish')>.","Clear the browser's cached Basic credentials (close all tabs / restart) and re-authenticate.","Move credentials to environment variables or a secrets store and never hardcode them in source."],"exampleFix":"// before\ncorrect_username_bytes = b\"stanleyjobson\"\ncorrect_password_bytes = b\"swordfish\"\n\n// after (load from env, keep constant-time compare)\nimport os\n_correct_u = os.environ[\"BASIC_USER\"].encode(\"utf8\")\n_correct_p = os.environ[\"BASIC_PASSWORD\"].encode(\"utf8\")\nis_correct_username = secrets.compare_digest(current_username_bytes, _correct_u)\nis_correct_password = secrets.compare_digest(current_password_bytes, _correct_p)","handlingStrategy":"validation","validationCode":"# Build the Basic header locally and sanity-check before sending\nimport base64, secrets\nUSER, PW = b\"stanleyjobson\", b\"swordfish\"\ndef basic_header(username: str, password: str) -> str | None:\n    if not (secrets.compare_digest(username.encode(), USER)\n            and secrets.compare_digest(password.encode(), PW)):\n        return None\n    token = base64.b64encode(f\"{username}:{password}\".encode()).decode()\n    return f\"Basic {token}\"","typeGuard":"from typing import TypeGuard\ndef is_valid_basic_pair(pair: tuple) -> TypeGuard[tuple[str, str]]:\n    u, p = pair\n    return isinstance(u, str) and isinstance(p, str) and u and p","tryCatchPattern":"import httpx\ntry:\n    r = httpx.get(\"/users/me\", headers={\"Authorization\": basic_header(u, p)})\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 401:\n        # browser will re-prompt via WWW-Authenticate: Basic\n        prompt_credentials()","preventionTips":["Construct the Basic header with base64('user:pass'); verify it round-trips.","Clear cached Basic credentials in the browser when they change.","Keep credentials in env/secrets, never hardcoded in source."],"tags":["fastapi","authentication","http-basic","secrets","python"],"backgroundTag":null,"analyzedSha":"3e8d1526d83a90aaf7d6eb6dc682bf150f180b25","analyzedAt":"2026-08-11T02:34:52.986Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}