[{"data":1,"prerenderedAt":88},["ShallowReactive",2],{"qa-\u002Ffastapi\u002Fsecurity\u002Fapi-keys":3},{"page":4,"siblings":79,"blog":70},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":69,"related":70,"seo":71,"seoDescription":72,"stem":73,"subtopic":74,"topic":75,"topicSlug":76,"updated":77,"__hash__":78},"qa\u002Ffastapi\u002Fsecurity\u002Fapi-keys.md","Api Keys",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","FastAPI","fastapi",{},true,3,"\u002Ffastapi\u002Fsecurity\u002Fapi-keys",[23,28,32,36,41,45,49,53,57,61,65],{"id":24,"difficulty":25,"q":26,"a":27},"api-key-header","easy","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":29,"difficulty":25,"q":30,"a":31},"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":33,"difficulty":25,"q":34,"a":35},"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":37,"difficulty":38,"q":39,"a":40},"multiple-auth-schemes","hard","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":42,"difficulty":14,"q":43,"a":44},"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":46,"difficulty":14,"q":47,"a":48},"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":50,"difficulty":38,"q":51,"a":52},"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":54,"difficulty":14,"q":55,"a":56},"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":58,"difficulty":14,"q":59,"a":60},"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":62,"difficulty":14,"q":63,"a":64},"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":66,"difficulty":14,"q":67,"a":68},"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",11,null,{"description":11},"FastAPI API key interview questions — APIKeyHeader, APIKeyQuery, APIKeyCookie, HTTPS, CORS, rate limiting and CSRF protection.","fastapi\u002Fsecurity\u002Fapi-keys","API Keys","Security & Auth","security","2026-06-20","hweGFrlIhWcir-N9DcspnE7K2iZ-Th8MrQXiIbqLccs",[80,84,87],{"subtopic":81,"path":82,"order":83},"OAuth2","\u002Ffastapi\u002Fsecurity\u002Foauth2",1,{"subtopic":85,"path":86,"order":12},"JWT Tokens","\u002Ffastapi\u002Fsecurity\u002Fjwt",{"subtopic":74,"path":21,"order":20},1782244113084]