{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "fastapi-api-key",
  "type": "registry:block",
  "title": "tneemo/fastapi-api-key",
  "description": "API key authentication for FastAPI: a require_api_key dependency that checks the X-API-Key header against keys from the environment, in constant time.",
  "author": "tneemo",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "app/security/__init__.py",
      "type": "registry:file",
      "target": "~/app/security/__init__.py",
      "content": "from app.security.api_key import require_api_key\n\n__all__ = [\"require_api_key\"]\n"
    },
    {
      "path": "app/security/api_key.py",
      "type": "registry:file",
      "target": "~/app/security/api_key.py",
      "content": "import hmac\nimport os\n\nfrom fastapi import HTTPException, Security, status\nfrom fastapi.security import APIKeyHeader\n\n_header = APIKeyHeader(name=\"X-API-Key\", auto_error=False)\n\n\ndef _accepted_keys() -> list[str]:\n    return [k.strip() for k in os.environ.get(\"API_KEYS\", \"\").split(\",\") if k.strip()]\n\n\ndef require_api_key(api_key: str | None = Security(_header)) -> str:\n    \"\"\"FastAPI dependency: 401 unless X-API-Key matches one of API_KEYS. Returns the key.\"\"\"\n    keys = _accepted_keys()\n    if not keys:\n        raise HTTPException(status.HTTP_503_SERVICE_UNAVAILABLE, \"API_KEYS is not configured\")\n    if api_key and any(hmac.compare_digest(api_key.encode(), k.encode()) for k in keys):\n        return api_key\n    raise HTTPException(\n        status.HTTP_401_UNAUTHORIZED,\n        \"Invalid or missing API key\",\n        headers={\"WWW-Authenticate\": \"APIKey\"},\n    )\n"
    },
    {
      "path": "tests/__init__.py",
      "type": "registry:file",
      "target": "~/tests/__init__.py",
      "content": ""
    },
    {
      "path": "tests/test_api_key.py",
      "type": "registry:file",
      "target": "~/tests/test_api_key.py",
      "content": "from fastapi import Depends, FastAPI\nfrom fastapi.testclient import TestClient\n\nfrom app.security import require_api_key\n\napp = FastAPI()\n\n\n@app.get(\"/secret\", dependencies=[Depends(require_api_key)])\ndef secret():\n    return {\"ok\": True}\n\n\nclient = TestClient(app)\n\n\ndef test_accepts_configured_keys(monkeypatch):\n    monkeypatch.setenv(\"API_KEYS\", \"one, two\")\n    assert client.get(\"/secret\", headers={\"X-API-Key\": \"two\"}).json() == {\"ok\": True}\n\n\ndef test_rejects_missing_or_wrong_keys(monkeypatch):\n    monkeypatch.setenv(\"API_KEYS\", \"one\")\n    assert client.get(\"/secret\").status_code == 401\n    assert client.get(\"/secret\", headers={\"X-API-Key\": \"nope\"}).status_code == 401\n\n\ndef test_unconfigured_is_503(monkeypatch):\n    monkeypatch.delenv(\"API_KEYS\", raising=False)\n    assert client.get(\"/secret\", headers={\"X-API-Key\": \"x\"}).status_code == 503\n"
    }
  ],
  "envVars": {
    "API_KEYS": "dev-key-change-me"
  },
  "docs": "1. Protect a whole router (for example in `app/main.py` of `tneemo/fastapi-service`):\n   ```python\n   from fastapi import Depends\n   from app.security import require_api_key\n\n   app.include_router(items.router, prefix=\"/items\", dependencies=[Depends(require_api_key)])\n   ```\n   or a single route: `@router.get(\"/\", dependencies=[Depends(require_api_key)])`.\n2. Set `API_KEYS` (comma-separated) in the environment or `.env`. In tests use `monkeypatch.setenv(\"API_KEYS\", \"test-key\")` and send `headers={\"X-API-Key\": \"test-key\"}`.\n",
  "categories": [
    "auth",
    "authentication",
    "api-key",
    "apikey",
    "security",
    "fastapi",
    "dependency",
    "header"
  ],
  "meta": {
    "pkg": "https://pkg.tneemo.com/tneemo/fastapi-api-key",
    "version": "1.0.0",
    "license": "MIT"
  }
}