Skip to content

Appliance API (api/server.py): bearer auth not declared as an OpenAPI security scheme - Swagger /docs has no Authorize button (authed calls 401) #8

Description

@UWillC

Summary

api/server.py enforces the bearer token by reading the Authorization header manually inside the check_auth dependency, but never registers a FastAPI/OpenAPI security scheme. The generated OpenAPI document therefore contains no securitySchemes and no security requirements, so:

  • Swagger UI (/docs) shows no "Authorize" button — there is no way to supply the token in the browser.
  • Every protected endpoint invoked from /docs (or via the Swagger-generated curl) is sent without an Authorization header and returns 401 {"detail":"Invalid or missing API token"}.

This makes the bundled Swagger UI unusable against any deployment that sets VIRP_API_TOKEN.

Environment

  • Repo at commit 604edca (api/server.py last modified b937b1a, 2026-04-24).
  • FastAPI app VIRP Appliance 0.1.0-poc, OpenAPI 3.1.0, uvicorn.
  • VIRP_API_TOKEN set (auth enforced).

Reproduce

  1. Run the API with VIRP_API_TOKEN=<token> and open /docs.
  2. GET /api/devices -> Execute -> 401 Invalid or missing API token.
  3. GET /openapi.json -> components has no securitySchemes; /api/devices GET has no security.

Expected

/docs shows an Authorize control; after entering the token once, Swagger attaches Authorization: Bearer <token> to every protected request and they succeed.

Root cause

check_auth(request: Request) reads request.headers.get("Authorization") directly — correct at runtime, but invisible to OpenAPI, so the schema advertises no auth and Swagger cannot prompt for it.

Suggested fix (minimal, backward-compatible)

Declare an HTTPBearer scheme and consume it via Security(...) so it lands in the OpenAPI doc, keeping the constant-time compare and the existing "no token set = dev mode" behaviour:

from fastapi import Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

bearer_scheme = HTTPBearer(auto_error=False)

async def check_auth(creds: HTTPAuthorizationCredentials | None = Security(bearer_scheme)):
    if not API_TOKEN:
        return  # dev/POC mode — no auth configured
    if creds is None or not hmac.compare_digest(creds.credentials, API_TOKEN):
        raise HTTPException(status_code=401, detail="Invalid or missing API token")

auto_error=False preserves the current 401 message and the unauthenticated dev-mode path. Existing clients that send Authorization: Bearer ... are unaffected; the only change is that OpenAPI now advertises the scheme and Swagger renders Authorize. (The audit_identity() header read can stay as-is.)

Notes

Found while standing up the bundled API in an isolated Cisco lab (read-only observation use). Happy to open a PR with the above if useful. Minor aside (not part of this issue): endpoints are served under /api/* while the integration spec describes /v1/*.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions