[{"data":1,"prerenderedAt":214},["ShallowReactive",2],{"topic-fastapi-security":3},{"framework":4,"topic":15,"subtopics":24},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":8,"stem":12,"tier":13,"__hash__":14},"frameworks\u002Fframeworks\u002Ffastapi.yml","FastAPI interview questions on async routing, Pydantic validation, dependency injection, OAuth2 security, database integration and deployment — the go-to Python framework for production APIs.","yml","fastapi",{},"FastAPI",6,"frameworks\u002Ffastapi",1,"lgr_X74wBdBYovbrlazGWPWqqi-YwNEUq44l1BmtgyE",{"id":16,"description":17,"extension":7,"frameworkSlug":8,"meta":18,"name":19,"order":20,"slug":21,"stem":22,"__hash__":23},"topics\u002Ftopics\u002Ffastapi-security.yml","OAuth2, JWT tokens and API keys — common FastAPI auth patterns that come up in both system-design and code-review interview rounds.",{},"Security & Auth",5,"security","topics\u002Ffastapi-security","1fvRyaYy_dE2_wzmizOLRbEhkO0ohphmkOIKnhHthGk",[25,95,154],{"id":26,"title":27,"body":28,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":37,"navigation":38,"order":13,"path":39,"questions":40,"questionsCount":87,"related":88,"seo":89,"seoDescription":90,"stem":91,"subtopic":92,"topic":19,"topicSlug":21,"updated":93,"__hash__":94},"qa\u002Ffastapi\u002Fsecurity\u002Foauth2.md","Oauth2",{"type":29,"value":30,"toc":31},"minimark",[],{"title":32,"searchDepth":33,"depth":33,"links":34},"",2,[],"hard","md",{},true,"\u002Ffastapi\u002Fsecurity\u002Foauth2",[41,46,50,54,58,62,67,71,75,79,83],{"id":42,"difficulty":43,"q":44,"a":45},"oauth2-password-bearer","medium","What is `OAuth2PasswordBearer` in FastAPI and what does it do?","`OAuth2PasswordBearer` is a callable security dependency that:\n1. Extracts the Bearer token from the `Authorization: Bearer \u003Ctoken>` header.\n2. Returns the token string to your handler.\n3. Registers the OAuth2 password flow in the OpenAPI schema so Swagger UI shows\n   an \"Authorize\" dialog.\n\n```python\nfrom fastapi.security import OAuth2PasswordBearer\nfrom fastapi import Depends\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\n@app.get(\"\u002Fme\")\nasync def current_user(token: str = Depends(oauth2_scheme)):\n    # token is the raw Bearer string — validate it yourself\n    payload = decode_jwt(token)\n    return payload\n```\n\n`OAuth2PasswordBearer` only extracts the token — it does NOT validate it.\nValidation is your responsibility.\n\nRule of thumb: use `OAuth2PasswordBearer` so Swagger UI gets the Authorize button;\ndo JWT decoding\u002Fverification in a separate `get_current_user` dependency.\n",{"id":47,"difficulty":43,"q":48,"a":49},"token-endpoint","How do you implement the `\u002Ftoken` endpoint for the OAuth2 password flow in FastAPI?","Accept `OAuth2PasswordRequestForm` (username + password as form data) and return\na token response:\n\n```python\nfrom fastapi import Depends\nfrom fastapi.security import OAuth2PasswordRequestForm\nfrom jose import jwt\n\n@app.post(\"\u002Ftoken\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n    user = await authenticate_user(form.username, form.password)\n    if not user:\n        raise HTTPException(\n            status_code=400, detail=\"Incorrect username or password\"\n        )\n    access_token = jwt.encode(\n        {\"sub\": str(user.id), \"exp\": datetime.utcnow() + timedelta(minutes=30)},\n        settings.secret_key, algorithm=\"HS256\"\n    )\n    return {\"access_token\": access_token, \"token_type\": \"bearer\"}\n```\n\nThe response must have `access_token` and `token_type` fields — Swagger UI\nreads these to store the token.\n\nRule of thumb: never return the user's password (even hashed) from `\u002Ftoken`;\nreturn only the token and its type.\n",{"id":51,"difficulty":43,"q":52,"a":53},"password-hashing","How do you hash and verify passwords securely in FastAPI?","Use `passlib` with `bcrypt`:\n\n```python\nfrom passlib.context import CryptContext\n\npwd_context = CryptContext(schemes=[\"bcrypt\"], deprecated=\"auto\")\n\ndef hash_password(plain: str) -> str:\n    return pwd_context.hash(plain)\n\ndef verify_password(plain: str, hashed: str) -> bool:\n    return pwd_context.verify(plain, hashed)\n\nasync def authenticate_user(username: str, password: str):\n    user = await db.get_user_by_username(username)\n    if not user or not verify_password(password, user.password_hash):\n        return None\n    return user\n```\n\nNever store plain-text passwords. `bcrypt` automatically includes a salt and\nis resistant to GPU-accelerated brute-force attacks.\n\nRule of thumb: use `passlib.CryptContext` with `bcrypt` — it handles salting,\nalgorithm upgrades (`deprecated=\"auto\"`) and constant-time comparison.\n",{"id":55,"difficulty":43,"q":56,"a":57},"get-current-user-dep","Show the standard pattern for a `get_current_user` dependency in FastAPI.","```python\nfrom fastapi import Depends, HTTPException\nfrom fastapi.security import OAuth2PasswordBearer\nfrom jose import jwt, JWTError\nfrom pydantic import BaseModel\n\noauth2_scheme = OAuth2PasswordBearer(tokenUrl=\"\u002Ftoken\")\n\nclass TokenData(BaseModel):\n    user_id: int\n\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    credentials_exception = HTTPException(\n        status_code=401,\n        detail=\"Could not validate credentials\",\n        headers={\"WWW-Authenticate\": \"Bearer\"},\n    )\n    try:\n        payload = jwt.decode(token, settings.secret_key, algorithms=[\"HS256\"])\n        user_id: int = payload.get(\"sub\")\n        if user_id is None:\n            raise credentials_exception\n    except JWTError:\n        raise credentials_exception\n    user = await db.get_user(user_id)\n    if not user:\n        raise credentials_exception\n    return user\n\n@app.get(\"\u002Fme\")\nasync def me(user: User = Depends(get_current_user)):\n    return user\n```\n\nRule of thumb: always include `headers={\"WWW-Authenticate\": \"Bearer\"}` on 401\nresponses — RFC 7235 requires it and some clients depend on it.\n",{"id":59,"difficulty":35,"q":60,"a":61},"oauth2-scopes","How do you implement OAuth2 scopes in FastAPI?","Define scopes in `OAuth2PasswordBearer`, encode them in the token, and verify\nthem with `SecurityScopes`:\n\n```python\nfrom fastapi import Security, SecurityScopes\nfrom fastapi.security import OAuth2PasswordBearer\n\noauth2_scheme = OAuth2PasswordBearer(\n    tokenUrl=\"\u002Ftoken\",\n    scopes={\"read\": \"Read items\", \"write\": \"Create and update items\"},\n)\n\nasync def get_current_user(\n    security_scopes: SecurityScopes,\n    token: str = Depends(oauth2_scheme),\n):\n    payload = jwt.decode(token, settings.secret_key, algorithms=[\"HS256\"])\n    token_scopes = payload.get(\"scopes\", [])\n    for scope in security_scopes.scopes:\n        if scope not in token_scopes:\n            raise HTTPException(\n                403,\n                detail=f\"Scope '{scope}' required\",\n                headers={\"WWW-Authenticate\": f'Bearer scope=\"{security_scopes.scope_str}\"'},\n            )\n    return payload\n\n@app.get(\"\u002Fitems\")\nasync def list_items(user = Security(get_current_user, scopes=[\"read\"])):\n    ...\n```\n\nUse `Security()` (not `Depends()`) to pass scope requirements.\n\nRule of thumb: encode granted scopes in the JWT at login time; verify required\nscopes in `get_current_user` — keep scope logic in the auth dependency, not handlers.\n",{"id":63,"difficulty":64,"q":65,"a":66},"inactive-user","easy","How do you block inactive or disabled users from accessing protected routes?","Add an `is_active` check in `get_current_user` or a separate `require_active_user`\ndependency:\n\n```python\nasync def get_current_active_user(\n    user: User = Depends(get_current_user),\n) -> User:\n    if not user.is_active:\n        raise HTTPException(status_code=400, detail=\"Inactive user\")\n    return user\n\n@app.get(\"\u002Forders\")\nasync def orders(user: User = Depends(get_current_active_user)):\n    ...\n```\n\nSeparating \"can we trust the token?\" (`get_current_user`) from \"is the user\nallowed?\" (`get_current_active_user`) makes the dependency graph cleaner and\neach function independently testable.\n\nRule of thumb: chain fine-grained permission checks as separate dependencies —\ndon't put all guard logic in one giant `get_current_user` function.\n",{"id":68,"difficulty":35,"q":69,"a":70},"refresh-token","How would you implement refresh tokens in a FastAPI application?","Issue two tokens at login: a short-lived access token and a long-lived refresh token.\nStore the refresh token hash in the DB. Provide a `\u002Frefresh` endpoint:\n\n```python\n@app.post(\"\u002Frefresh\")\nasync def refresh_token(refresh_token: str = Body(...)):\n    # 1. Verify it's a valid JWT\n    try:\n        payload = jwt.decode(refresh_token, REFRESH_SECRET, algorithms=[\"HS256\"])\n    except JWTError:\n        raise HTTPException(401, \"Invalid refresh token\")\n\n    # 2. Check it exists and isn't revoked in the DB\n    stored = await db.get_refresh_token(payload[\"jti\"])\n    if not stored or stored.revoked:\n        raise HTTPException(401, \"Refresh token revoked\")\n\n    # 3. Issue new access token (optionally rotate refresh token)\n    new_access = create_access_token(payload[\"sub\"])\n    return {\"access_token\": new_access, \"token_type\": \"bearer\"}\n```\n\nKey design decisions: rotate refresh tokens on each use (reduces replay risk),\nstore a `jti` (JWT ID) for revocation, use a different signing secret for refresh tokens.\n\nRule of thumb: keep access tokens short (15-30 min); keep refresh tokens\nlong (7-30 days) but revocable via a DB lookup.\n",{"id":72,"difficulty":35,"q":73,"a":74},"logout-token-revocation","How do you implement logout \u002F token revocation for JWT-based auth in FastAPI?","JWTs are stateless — you can't \"un-sign\" one. Revocation requires a server-side\nstore:\n\n**Option A — Revocation list (deny list)**:\n```python\n@app.post(\"\u002Flogout\")\nasync def logout(token: str = Depends(oauth2_scheme)):\n    payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n    jti = payload[\"jti\"]\n    exp = payload[\"exp\"]\n    await redis.setex(f\"revoked:{jti}\", exp - int(time.time()), \"1\")\n    return {\"detail\": \"Logged out\"}\n\n# In get_current_user, after decoding:\nif await redis.exists(f\"revoked:{payload['jti']}\"):\n    raise HTTPException(401, \"Token revoked\")\n```\n\n**Option B — Short expiry + refresh rotation** (no revocation list needed):\nAccess token expires in 5 minutes; compromised tokens are useless quickly.\n\nRule of thumb: for low-security APIs, use short-lived tokens; for high-security\napps (banking, health), maintain a revocation list in Redis keyed by JWT ID.\n",{"id":76,"difficulty":64,"q":77,"a":78},"oauth2-form-vs-json","Why does the OAuth2 `\u002Ftoken` endpoint accept form data instead of JSON?","The **OAuth2 specification (RFC 6749)** requires the token endpoint to accept\n`application\u002Fx-www-form-urlencoded` data — not JSON. This is for historical\ncompatibility with web browsers and existing OAuth2 clients.\n\nFastAPI's `OAuth2PasswordRequestForm` reads:\n- `username` (form field)\n- `password` (form field)\n- `scope` (optional, space-separated)\n- `grant_type` (must be `\"password\"`)\n\n```python\n@app.post(\"\u002Ftoken\")\nasync def login(form: OAuth2PasswordRequestForm = Depends()):\n    # form.username, form.password, form.scopes\n    ...\n```\n\nSwagger UI's \"Authorize\" dialog sends this form automatically.\n\nRule of thumb: never change the `\u002Ftoken` endpoint to JSON — it breaks OAuth2\nlibrary compatibility; keep all other API endpoints as JSON.\n",{"id":80,"difficulty":35,"q":81,"a":82},"cookie-auth","How do you implement cookie-based authentication in FastAPI?","Use `APIKeyCookie` from `fastapi.security` or manually read the cookie:\n\n```python\nfrom fastapi import Cookie, Response\nfrom fastapi.security import APIKeyCookie\n\ncookie_scheme = APIKeyCookie(name=\"session_id\")\n\n@app.post(\"\u002Flogin\")\nasync def login(response: Response, form: OAuth2PasswordRequestForm = Depends()):\n    user = await authenticate_user(form.username, form.password)\n    if not user:\n        raise HTTPException(400, \"Invalid credentials\")\n    token = create_session_token(user.id)\n    response.set_cookie(\n        key=\"session_id\",\n        value=token,\n        httponly=True,    # no JS access\n        secure=True,      # HTTPS only\n        samesite=\"lax\",\n    )\n    return {\"status\": \"logged in\"}\n\n@app.get(\"\u002Fme\")\nasync def me(session: str = Depends(cookie_scheme)):\n    user_id = validate_session(session)\n    return await db.get_user(user_id)\n```\n\nRule of thumb: set `httponly=True`, `secure=True`, and `samesite=\"lax\"` on\nauth cookies — these three flags prevent XSS theft, HTTP sniffing, and CSRF.\n",{"id":84,"difficulty":35,"q":85,"a":86},"two-factor-auth","How would you add TOTP two-factor authentication to a FastAPI app?","Use the `pyotp` library to generate and verify TOTP codes:\n\n```python\nimport pyotp\n\n@app.post(\"\u002F2fa\u002Fsetup\")\nasync def setup_2fa(user: User = Depends(get_current_user)):\n    secret = pyotp.random_base32()\n    await db.save_totp_secret(user.id, secret)\n    totp = pyotp.TOTP(secret)\n    return {\"provisioning_uri\": totp.provisioning_uri(user.email, issuer_name=\"MyApp\")}\n\n@app.post(\"\u002F2fa\u002Fverify\")\nasync def verify_2fa(code: str = Body(...), user: User = Depends(get_current_user)):\n    secret = await db.get_totp_secret(user.id)\n    if not pyotp.TOTP(secret).verify(code):\n        raise HTTPException(401, \"Invalid 2FA code\")\n    # issue the final access token\n    return {\"access_token\": create_access_token(user.id), \"token_type\": \"bearer\"}\n```\n\nThe flow: login → partial token → `\u002F2fa\u002Fverify` with code → full access token.\n\nRule of thumb: issue a scoped \"pending 2FA\" token after password verification;\nonly issue a full-access token after TOTP is confirmed.\n",11,null,{"description":32},"FastAPI OAuth2 interview questions — OAuth2PasswordBearer, password flow, token endpoint, scopes and securing routes with OAuth2.","fastapi\u002Fsecurity\u002Foauth2","OAuth2","2026-06-20","VfoQ1II42taw05rn_T0KjOE4Ue0T3JvJjExIxKJz_hg",{"id":96,"title":97,"body":98,"description":32,"difficulty":35,"extension":36,"framework":10,"frameworkSlug":8,"meta":102,"navigation":38,"order":33,"path":103,"questions":104,"questionsCount":87,"related":88,"seo":149,"seoDescription":150,"stem":151,"subtopic":152,"topic":19,"topicSlug":21,"updated":93,"__hash__":153},"qa\u002Ffastapi\u002Fsecurity\u002Fjwt.md","Jwt",{"type":29,"value":99,"toc":100},[],{"title":32,"searchDepth":33,"depth":33,"links":101},[],{},"\u002Ffastapi\u002Fsecurity\u002Fjwt",[105,109,113,117,121,125,129,133,137,141,145],{"id":106,"difficulty":64,"q":107,"a":108},"what-is-jwt","What is a JWT and what are its three parts?","A **JSON Web Token** (JWT) is a compact, URL-safe string that encodes a signed\nJSON payload. It has three dot-separated Base64URL-encoded parts:\n\n```\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9   ← Header\n.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxN}  ← Payload (claims)\n.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c  ← Signature\n```\n\n- **Header**: algorithm and token type (`{\"alg\": \"HS256\", \"typ\": \"JWT\"}`).\n- **Payload**: claims — standard (`sub`, `exp`, `iat`) and custom (`role`, `scopes`).\n- **Signature**: HMAC or RSA\u002FEC signature over header + payload.\n\nJWTs are **not encrypted by default** — the payload is only Base64-encoded.\nAnyone can decode it. The signature only proves it wasn't tampered with.\n\nRule of thumb: never put sensitive data (passwords, PII) in JWT payload — use\nJWE (encrypted JWT) if you need to protect the payload contents.\n",{"id":110,"difficulty":43,"q":111,"a":112},"encode-decode-jwt","How do you encode and decode a JWT in a FastAPI app?","Install `python-jose[cryptography]` and use `jose.jwt`:\n\n```python\nfrom datetime import datetime, timedelta, timezone\nfrom jose import jwt, JWTError\n\nSECRET_KEY = \"your-256-bit-secret\"\nALGORITHM = \"HS256\"\n\ndef create_access_token(user_id: int, expires_minutes: int = 30) -> str:\n    payload = {\n        \"sub\": str(user_id),\n        \"exp\": datetime.now(timezone.utc) + timedelta(minutes=expires_minutes),\n        \"iat\": datetime.now(timezone.utc),\n    }\n    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)\n\ndef decode_access_token(token: str) -> dict:\n    try:\n        return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])\n    except JWTError:\n        raise HTTPException(401, \"Invalid token\")\n```\n\nRule of thumb: always pass `algorithms=` as a list (not a string) to `jwt.decode()` —\nthe list prevents algorithm-confusion attacks where an attacker switches from RS256 to HS256.\n",{"id":114,"difficulty":43,"q":115,"a":116},"jwt-expiry","How does JWT expiry work and how does FastAPI check it?","The `exp` claim is a Unix timestamp. `python-jose` automatically validates it\nduring `jwt.decode()` — expired tokens raise `ExpiredSignatureError` (a subclass\nof `JWTError`).\n\n```python\nfrom jose import ExpiredSignatureError, JWTError\n\nasync def get_current_user(token: str = Depends(oauth2_scheme)):\n    try:\n        payload = jwt.decode(token, SECRET_KEY, algorithms=[\"HS256\"])\n    except ExpiredSignatureError:\n        raise HTTPException(401, \"Token has expired\")\n    except JWTError:\n        raise HTTPException(401, \"Invalid token\")\n    return payload\n```\n\nClock skew: `python-jose` allows a small tolerance via `options={\"leeway\": 10}` (10 seconds).\n\nRule of thumb: catch `ExpiredSignatureError` separately from other `JWTError` subtypes\nso you can give clients a specific \"please refresh\" error instead of a generic \"invalid token\".\n",{"id":118,"difficulty":43,"q":119,"a":120},"jwt-claims","What standard JWT claims should you include and what do they mean?","| Claim | Name | Meaning |\n|-------|------|---------|\n| `sub` | Subject | Who the token is about (user ID) |\n| `exp` | Expiration | Unix timestamp when token expires |\n| `iat` | Issued At | Unix timestamp when token was created |\n| `nbf` | Not Before | Token not valid before this timestamp |\n| `jti` | JWT ID | Unique ID for revocation |\n| `iss` | Issuer | Who created the token |\n| `aud` | Audience | Who the token is intended for |\n\n```python\npayload = {\n    \"sub\": str(user.id),\n    \"exp\": utcnow + timedelta(minutes=30),\n    \"iat\": utcnow,\n    \"jti\": str(uuid4()),     # for revocation\n    \"iss\": \"https:\u002F\u002Fmyapi.example.com\",\n}\n```\n\nRule of thumb: always set `sub`, `exp`, and `iat`; add `jti` if you need\ntoken revocation; add `iss`\u002F`aud` for multi-service architectures.\n",{"id":122,"difficulty":35,"q":123,"a":124},"hs256-vs-rs256","What is the difference between HS256 and RS256 JWT signing and when do you use each?","| | HS256 (HMAC) | RS256 (RSA) |\n|---|---|---|\n| Key type | Shared secret | Public\u002Fprivate key pair |\n| Who can sign | Anyone with the secret | Only the private key holder |\n| Who can verify | Anyone with the secret | Anyone with the public key |\n| Use case | Single service | Multiple services \u002F external clients |\n\n```python\n# HS256 — single service\njwt.encode(payload, \"shared-secret\", algorithm=\"HS256\")\njwt.decode(token,  \"shared-secret\", algorithms=[\"HS256\"])\n\n# RS256 — multi-service (sign with private, verify with public)\njwt.encode(payload, private_key, algorithm=\"RS256\")\njwt.decode(token,  public_key,   algorithms=[\"RS256\"])\n```\n\nWith RS256, you can publish the public key (JWKS endpoint) so other services\nverify tokens without ever seeing the private key.\n\nRule of thumb: use HS256 for simple single-service auth; use RS256 when tokens\nneed to be verified by external services or published via a JWKS endpoint.\n",{"id":126,"difficulty":35,"q":127,"a":128},"algorithm-confusion","What is the JWT algorithm confusion attack and how does FastAPI prevent it?","If a service uses RS256 but `jwt.decode()` is called with `algorithms=[\"RS256\", \"HS256\"]`,\nan attacker can forge a token by taking the RS256 public key (which is public),\nusing it as an HMAC secret, and signing a new token with HS256. The server\naccepts it because HS256 is allowed.\n\nPrevention:\n```python\n# SAFE — only allow the expected algorithm\njwt.decode(token, PUBLIC_KEY, algorithms=[\"RS256\"])   # never include HS256\n\n# DANGEROUS\njwt.decode(token, PUBLIC_KEY, algorithms=[\"RS256\", \"HS256\"])\n```\n\nAlso: never use `algorithms=None` or skip algorithm validation.\n\nRule of thumb: always pin the algorithm to exactly one value matching what you\nuse to sign — allow-listing multiple algorithms invites confusion attacks.\n",{"id":130,"difficulty":35,"q":131,"a":132},"jwt-none-attack","What is the JWT \"none\" algorithm attack?","Early JWT libraries accepted tokens with `\"alg\": \"none\"` — meaning no signature\nis needed. An attacker could forge any payload, set `alg: none`, and the library\nwould accept it without any verification.\n\n```\n# Malicious token — unsigned\n{\"alg\": \"none\", \"typ\": \"JWT\"}.{\"sub\": \"admin\"}.\n```\n\nModern libraries (`python-jose`, `PyJWT`) reject `none` by default. Ensure\n`\"none\"` is not in your `algorithms=` list:\n\n```python\njwt.decode(token, SECRET, algorithms=[\"HS256\"])   # \"none\" not listed → rejected\n```\n\nRule of thumb: never include `\"none\"` in `algorithms=`; if your library has a\n`verify_signature=False` option, never use it in production.\n",{"id":134,"difficulty":43,"q":135,"a":136},"jwt-storage","Where should JWTs be stored on the client side, and what are the security trade-offs?","| Storage | XSS risk | CSRF risk | Notes |\n|---------|----------|-----------|-------|\n| `localStorage` | High | None | JS-accessible — token stolen by XSS |\n| `sessionStorage` | High | None | Same as localStorage but tab-scoped |\n| `httpOnly` cookie | None | Medium | Inaccessible to JS; add SameSite=Lax |\n\nBest practice — **`httpOnly`, `Secure`, `SameSite=Lax` cookie**:\n```python\nresponse.set_cookie(\n    key=\"access_token\",\n    value=token,\n    httponly=True,\n    secure=True,\n    samesite=\"lax\",\n    max_age=1800,\n)\n```\n\n`SameSite=Lax` blocks cross-site POST requests (CSRF for state-changing operations)\nwhile allowing cross-site GET navigation.\n\nRule of thumb: store tokens in `httpOnly` cookies, not `localStorage` — XSS is\nmore common than CSRF, and `httpOnly` provides a meaningful defence against it.\n",{"id":138,"difficulty":35,"q":139,"a":140},"token-rotation","What is refresh token rotation and why is it more secure than long-lived access tokens?","**Refresh token rotation** issues a new refresh token on every use and immediately\ninvalidates the old one. If a stolen refresh token is used, the legitimate user's\nnext request will fail (old token invalid) — alerting you to a compromise.\n\n```python\n@app.post(\"\u002Frefresh\")\nasync def refresh(old_token: str = Body(...)):\n    payload = decode_refresh_token(old_token)\n    jti = payload[\"jti\"]\n\n    # Check old token exists and isn't already used\n    stored = await db.get_refresh_token(jti)\n    if not stored or stored.used:\n        # Token reuse detected — revoke all user tokens\n        await db.revoke_all_tokens(payload[\"sub\"])\n        raise HTTPException(401, \"Refresh token reuse detected\")\n\n    await db.mark_used(jti)\n    new_refresh = create_refresh_token(payload[\"sub\"])\n    new_access  = create_access_token(payload[\"sub\"])\n    await db.store_refresh_token(new_refresh)\n    return {\"access_token\": new_access, \"refresh_token\": new_refresh}\n```\n\nRule of thumb: implement refresh token rotation for any app with sensitive data —\na detected reuse should trigger automatic revocation of all user sessions.\n",{"id":142,"difficulty":35,"q":143,"a":144},"jwt-decode-without-verify","How do you decode a JWT payload without verifying the signature, and when is this safe?","Pass `options={\"verify_signature\": False}` to `jwt.decode()` in `python-jose`,\nor use `PyJWT` with `algorithms=[]`:\n\n```python\n# python-jose\npayload = jwt.decode(\n    token,\n    key=\"\",\n    algorithms=[\"HS256\"],\n    options={\"verify_signature\": False},\n)\n```\n\nThis is **never safe** for authenticating users. Valid uses:\n- Logging the `sub` from an **already-verified** token that expired (for debugging).\n- Inspecting headers to decide which key\u002Falgorithm to use before re-decoding with verification.\n\nRule of thumb: never use unverified JWT decoding in the auth path — it is only\nfor tooling and debugging flows where the token has already been verified elsewhere.\n",{"id":146,"difficulty":64,"q":147,"a":148},"jwt-size","What are the practical size limits for JWT tokens and how do they affect HTTP?","JWTs have no spec-defined size limit, but practical limits exist:\n- **HTTP headers**: servers typically cap individual headers at 8 KB.\n- **Cookies**: limited to 4 KB per cookie.\n- **Typical JWT**: a token with 5-10 claims is 200-500 bytes after Base64URL encoding.\n\nProblems arise when you embed large payloads (roles list, user profile) in the token:\n\n```python\n# BAD — embedding permissions in token can push it over 4 KB\npayload = {\"sub\": \"1\", \"permissions\": list_of_200_permissions}\n\n# GOOD — store just the user ID; look up permissions from DB\u002Fcache\npayload = {\"sub\": \"1\", \"exp\": ..., \"role\": \"admin\"}\n```\n\nRule of thumb: keep JWT payloads under 1 KB — store only stable identity claims\n(`sub`, `role`, `scopes`); look up volatile permissions from cache at request time.\n",{"description":32},"FastAPI JWT interview questions — encoding, decoding, python-jose, expiry, claims, signature verification and common JWT pitfalls.","fastapi\u002Fsecurity\u002Fjwt","JWT Tokens","a8L1LKxnRwm7dhkGZHtjql_sWeh8ZBuxGKpnQdkRifU",{"id":155,"title":156,"body":157,"description":32,"difficulty":43,"extension":36,"framework":10,"frameworkSlug":8,"meta":161,"navigation":38,"order":162,"path":163,"questions":164,"questionsCount":87,"related":88,"seo":209,"seoDescription":210,"stem":211,"subtopic":212,"topic":19,"topicSlug":21,"updated":93,"__hash__":213},"qa\u002Ffastapi\u002Fsecurity\u002Fapi-keys.md","Api Keys",{"type":29,"value":158,"toc":159},[],{"title":32,"searchDepth":33,"depth":33,"links":160},[],{},3,"\u002Ffastapi\u002Fsecurity\u002Fapi-keys",[165,169,173,177,181,185,189,193,197,201,205],{"id":166,"difficulty":64,"q":167,"a":168},"api-key-header","How do you implement API key authentication via a header in FastAPI?","Use `APIKeyHeader` from `fastapi.security`:\n\n```python\nfrom fastapi import Depends, HTTPException\nfrom fastapi.security.api_key import APIKeyHeader\n\nAPI_KEY = \"my-secret-key\"\napi_key_header = APIKeyHeader(name=\"X-API-Key\")\n\nasync def verify_api_key(api_key: str = Depends(api_key_header)):\n    if api_key != API_KEY:\n        raise HTTPException(status_code=403, detail=\"Invalid API key\")\n    return api_key\n\n@app.get(\"\u002Fdata\", dependencies=[Depends(verify_api_key)])\nasync def get_data():\n    return {\"data\": \"secret\"}\n```\n\n`APIKeyHeader` extracts the named header and registers the scheme in OpenAPI.\n\nRule of thumb: use headers for API keys (not query params) — headers are less\nlikely to appear in server access logs and browser history.\n",{"id":170,"difficulty":64,"q":171,"a":172},"api-key-query","How do you accept an API key as a query parameter in FastAPI?","Use `APIKeyQuery`:\n\n```python\nfrom fastapi.security.api_key import APIKeyQuery\n\napi_key_query = APIKeyQuery(name=\"api_key\")\n\nasync def verify_key(key: str = Depends(api_key_query)):\n    if key != settings.api_key:\n        raise HTTPException(403, \"Invalid API key\")\n\n@app.get(\"\u002Fexport\", dependencies=[Depends(verify_key)])\nasync def export():\n    ...\n# GET \u002Fexport?api_key=abc123\n```\n\nQuery param keys are **visible in logs, browser history, and URLs shared\naccidentally** — avoid them for sensitive APIs.\n\nRule of thumb: only use query param API keys for webhooks or download links\nwhere adding a header is impossible; always prefer header-based keys.\n",{"id":174,"difficulty":64,"q":175,"a":176},"api-key-cookie","How do you use `APIKeyCookie` for session-based authentication?","```python\nfrom fastapi.security.api_key import APIKeyCookie\n\ncookie_scheme = APIKeyCookie(name=\"session\")\n\nasync def get_session(session: str = Depends(cookie_scheme)):\n    user_id = validate_session_token(session)\n    if not user_id:\n        raise HTTPException(403, \"Invalid session\")\n    return user_id\n\n@app.get(\"\u002Fprofile\")\nasync def profile(user_id: int = Depends(get_session)):\n    return await db.get_user(user_id)\n```\n\nFor security, always set `httponly=True` and `secure=True` when setting the\nsession cookie in the login endpoint.\n\nRule of thumb: use `APIKeyCookie` for browser-based sessions; add CSRF\nprotection (double-submit cookie or `SameSite=Lax`) for any state-changing routes.\n",{"id":178,"difficulty":35,"q":179,"a":180},"multiple-auth-schemes","How do you support multiple authentication methods (header key or Bearer token) on the same endpoint?","Declare both dependencies and make them optional; raise if neither is provided:\n\n```python\nfrom fastapi.security import HTTPBearer, APIKeyHeader\n\nbearer = HTTPBearer(auto_error=False)\napi_key_header = APIKeyHeader(name=\"X-API-Key\", auto_error=False)\n\nasync def authenticate(\n    bearer_token: HTTPAuthorizationCredentials | None = Depends(bearer),\n    api_key: str | None = Depends(api_key_header),\n):\n    if bearer_token:\n        return verify_jwt(bearer_token.credentials)\n    if api_key:\n        return verify_api_key(api_key)\n    raise HTTPException(401, \"Authentication required\")\n\n@app.get(\"\u002Fdata\")\nasync def data(user = Depends(authenticate)):\n    return user\n```\n\n`auto_error=False` prevents FastAPI from raising 403 when the header is absent,\ngiving your code a chance to check the alternative.\n\nRule of thumb: set `auto_error=False` on all optional schemes and raise\nexplicitly at the end — otherwise FastAPI returns 403 as soon as one scheme is missing.\n",{"id":182,"difficulty":43,"q":183,"a":184},"https-tls","Why must FastAPI APIs run over HTTPS and how do you enforce it?","Without HTTPS, API keys and JWTs in headers are transmitted in plaintext —\nanyone on the same network can intercept them.\n\nIn production, TLS termination is handled by the reverse proxy (Nginx, Caddy,\nAWS ALB) — not Uvicorn itself:\n\n```nginx\nserver {\n    listen 443 ssl;\n    ssl_certificate     \u002Fetc\u002Fssl\u002Fcert.pem;\n    ssl_certificate_key \u002Fetc\u002Fssl\u002Fkey.pem;\n    location \u002F { proxy_pass http:\u002F\u002F127.0.0.1:8000; }\n}\n```\n\nIn FastAPI, redirect HTTP → HTTPS with `HTTPSRedirectMiddleware`:\n\n```python\nfrom starlette.middleware.httpsredirect import HTTPSRedirectMiddleware\napp.add_middleware(HTTPSRedirectMiddleware)\n```\n\nRule of thumb: terminate TLS at the load balancer\u002Freverse proxy; use\n`HTTPSRedirectMiddleware` as a safety net to catch direct HTTP connections.\n",{"id":186,"difficulty":43,"q":187,"a":188},"cors","What is CORS and how do you configure it in FastAPI?","**CORS** (Cross-Origin Resource Sharing) is a browser security policy. A browser\nblocks JavaScript from reading a response from a different origin unless the server\nincludes the appropriate `Access-Control-Allow-*` headers.\n\nFastAPI provides `CORSMiddleware`:\n\n```python\nfrom fastapi.middleware.cors import CORSMiddleware\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"https:\u002F\u002Fmyapp.example.com\"],  # never \"*\" in production with credentials\n    allow_credentials=True,\n    allow_methods=[\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n    allow_headers=[\"Content-Type\", \"Authorization\"],\n)\n```\n\n`allow_origins=[\"*\"]` is fine for public APIs with no auth; for credentialed\nrequests (cookies, auth headers) you must list exact origins.\n\nRule of thumb: list exact allowed origins rather than `\"*\"` for any API that\nhandles auth — browsers won't send credentials to `\"*\"` anyway, and it forces\nyou to be explicit.\n",{"id":190,"difficulty":35,"q":191,"a":192},"csrf-protection","When is CSRF protection needed in a FastAPI application and how do you implement it?","CSRF (Cross-Site Request Forgery) attacks are relevant when you use **cookie-based\nauth**. JWT in `Authorization: Bearer` headers is immune to CSRF because\ncross-origin forms can't set custom headers.\n\nFor cookie auth, implement the **Double Submit Cookie** pattern:\n\n```python\n@app.post(\"\u002Flogin\")\nasync def login(response: Response, ...):\n    csrf_token = secrets.token_urlsafe(32)\n    response.set_cookie(\"session\", session_token, httponly=True, secure=True)\n    response.set_cookie(\"csrf_token\", csrf_token, secure=True)   # NOT httponly\n    return {\"csrf_token\": csrf_token}\n\n# Client sends X-CSRF-Token header with every mutating request\n@app.post(\"\u002Forders\")\nasync def create_order(\n    x_csrf_token: str = Header(),\n    csrf_cookie: str = Cookie(alias=\"csrf_token\"),\n):\n    if x_csrf_token != csrf_cookie:\n        raise HTTPException(403, \"CSRF token mismatch\")\n```\n\n`SameSite=Lax` on session cookies also blocks most CSRF without a token.\n\nRule of thumb: if using cookie auth, set `SameSite=Lax` as the minimum; add\nthe double-submit CSRF token for state-changing endpoints (POST, PUT, DELETE).\n",{"id":194,"difficulty":43,"q":195,"a":196},"rate-limiting","How do you implement rate limiting in FastAPI?","Use a dependency that checks a Redis counter:\n\n```python\nimport time\nfrom fastapi import Depends, HTTPException, Request\n\nasync def rate_limit(request: Request, calls: int = 100, period: int = 60):\n    key = f\"rate:{request.client.host}\"\n    pipe = redis.pipeline()\n    pipe.incr(key)\n    pipe.expire(key, period)\n    count, _ = await pipe.execute()\n    if count > calls:\n        raise HTTPException(\n            status_code=429,\n            detail=\"Too many requests\",\n            headers={\"Retry-After\": str(period)},\n        )\n\n@app.get(\"\u002Fitems\", dependencies=[Depends(rate_limit)])\nasync def list_items():\n    ...\n```\n\nFor production, use `slowapi` (wraps `limits` library with FastAPI integration):\n```python\nfrom slowapi import Limiter\nlimiter = Limiter(key_func=get_remote_address)\n```\n\nRule of thumb: implement rate limiting at the reverse proxy for brute-force\ndefence; implement in the app for per-user\u002Fper-token granular limits.\n",{"id":198,"difficulty":43,"q":199,"a":200},"sensitive-data-in-logs","How do you prevent API keys and tokens from appearing in FastAPI logs?","FastAPI\u002FUvicorn access logs include the full URL path with query params —\nAPI keys in query params will appear in logs.\n\nMitigations:\n1. **Use headers, not query params**, for API keys (they're not in access logs).\n2. **Custom log filter** to redact header values:\n\n```python\nimport logging\n\nclass RedactAuthFilter(logging.Filter):\n    def filter(self, record: logging.LogRecord) -> bool:\n        msg = str(record.getMessage())\n        record.msg = msg.replace(settings.api_key, \"***\")\n        return True\n```\n\n3. For Uvicorn's access log, use `--no-access-log` and write your own structured\n   middleware that explicitly controls what's logged.\n\nRule of thumb: never put auth credentials in query params; treat all incoming\n`Authorization` header values as secrets and never log them verbatim.\n",{"id":202,"difficulty":43,"q":203,"a":204},"trusted-hosts-middleware","What does `TrustedHostMiddleware` do in FastAPI?","`TrustedHostMiddleware` rejects requests whose `Host` header doesn't match an\nallowed list, preventing **Host header injection** attacks.\n\n```python\nfrom starlette.middleware.trustedhost import TrustedHostMiddleware\n\napp.add_middleware(\n    TrustedHostMiddleware,\n    allowed_hosts=[\"api.example.com\", \"*.example.com\"],\n)\n```\n\nWithout it, an attacker who can route traffic to your server can send a request\nwith `Host: evil.com` — this can affect password reset links, CORS checks that\nrely on the `Host` header, and server-generated URLs.\n\nRule of thumb: add `TrustedHostMiddleware` in production with the exact\nhostname(s) — it's a one-liner defence against Host header injection.\n",{"id":206,"difficulty":43,"q":207,"a":208},"sql-injection-prevention","How does FastAPI\u002FPydantic help prevent SQL injection, and what should you still watch out for?","Pydantic validates and coerces input types — but it does **not** sanitise SQL.\nSQL injection prevention requires using parameterised queries (ORM or raw):\n\n```python\n# SAFE — SQLAlchemy parameterises automatically\nresult = await db.execute(select(User).where(User.name == username))\n\n# SAFE — raw SQL with bound parameters\nawait db.execute(text(\"SELECT * FROM users WHERE name = :name\"), {\"name\": username})\n\n# DANGEROUS — string interpolation\nawait db.execute(f\"SELECT * FROM users WHERE name = '{username}'\")\n```\n\nPydantic validates that `username` is a `str` — but a string can still contain\nSQL metacharacters. Always use parameterised queries regardless of validation.\n\nRule of thumb: let the ORM\u002Fquery builder parameterise for you; never interpolate\nuser input directly into SQL strings, even after Pydantic validation.\n",{"description":32},"FastAPI API key interview questions — APIKeyHeader, APIKeyQuery, APIKeyCookie, HTTPS, CORS, rate limiting and CSRF protection.","fastapi\u002Fsecurity\u002Fapi-keys","API Keys","hweGFrlIhWcir-N9DcspnE7K2iZ-Th8MrQXiIbqLccs",1782244096229]