Skip to main content

Starlette/FastAPI middleware and route builders for protecting HTTP APIs with Keycard OAuth

Project description

keycardai-starlette

Starlette/FastAPI integration for Keycard. Plugs into Starlette's standard authentication framework: an AuthenticationBackend populates request.user and request.auth, the @requires decorator gates routes, and @auth.grant(resource) performs delegated OAuth 2.0 token exchange.

Installation

pip install keycardai-starlette

Quick Start

from fastapi import FastAPI, Request
from keycardai.starlette import AuthProvider, KeycardUser, requires
from keycardai.oauth.server import AccessContext, ClientSecret

auth = AuthProvider(
    zone_id="your-zone-id",
    application_credential=ClientSecret(("client_id", "client_secret")),
)

app = FastAPI()
auth.install(app)  # AuthenticationMiddleware + /.well-known/* routes

@app.get("/health")
async def health():
    return {"ok": True}                    # public, no decorator

@app.get("/api/me")
@requires("authenticated")                 # standard Starlette gating
async def me(request: Request):
    user: KeycardUser = request.user
    return {"client_id": user.client_id, "scopes": list(request.auth.scopes)}

@app.get("/api/data")
@requires("authenticated")
@auth.grant("https://api.example.com")     # delegated token exchange (RFC 8693)
async def get_data(request: Request, access: AccessContext):
    token = access.access("https://api.example.com").access_token

How it integrates with Starlette

AuthProvider.install(app) does two things:

  1. Adds starlette.middleware.authentication.AuthenticationMiddleware wired to a KeycardAuthBackend so every request gets a populated request.user and request.auth.
  2. Mounts the OAuth discovery endpoints under /.well-known/.

Routes you do not decorate stay public: the backend returns None (anonymous user) when no Authorization header is present, exactly like starlette.authentication.UnauthenticatedUser. Routes that need a verified caller use the @requires(...) decorator.

Decorators

@requires(scopes)

keycardai.starlette.requires is a drop-in for starlette.authentication.requires with one difference: anonymous requests get an RFC 6750 401 response with a WWW-Authenticate: Bearer ... resource_metadata="..." header (RFC 9728) instead of stock HTTPException(403). Scope checks behave the same.

@requires("authenticated")              # any verified caller
@requires(["authenticated", "admin"])   # additional scope check

AuthProvider.requires is exposed as a static-method alias if you prefer accessing the decorator via the provider instance:

@auth.requires("authenticated")

@auth.grant(resource)

Performs OAuth 2.0 delegated token exchange (RFC 8693) for one or more downstream resources and injects an AccessContext parameter into the endpoint. Mirrors the @grant() decorator from keycardai-mcp so the decorator name is consistent across packages.

@app.get("/api/calendar")
@requires("authenticated")
@auth.grant("https://graph.microsoft.com")
async def calendar(request: Request, access: AccessContext):
    token = access.access("https://graph.microsoft.com").access_token

Errors from the exchange are stored per-resource on the AccessContext rather than raised: call access.has_errors() / access.get_errors() to decide how to respond. The AccessContext parameter is hidden from FastAPI introspection via __signature__ rewriting, so it never appears in the generated OpenAPI schema.

Other entry points

protected_router()

Mount any ASGI app behind Keycard authentication and the /.well-known/* metadata routes in one call. Useful when every route under some prefix needs the same protection (for example an MCP transport, an internal admin app).

from keycardai.starlette import protected_router
from starlette.applications import Starlette

inner = build_my_api()  # any ASGI app

app = Starlette(routes=protected_router(
    issuer=auth.issuer,
    app=inner,
    verifier=auth.get_token_verifier(),
))

AuthenticationMiddleware directly

For full control over middleware ordering, register the standard Starlette middleware yourself:

from starlette.middleware.authentication import AuthenticationMiddleware
from keycardai.starlette import KeycardAuthBackend, keycard_on_error

app.add_middleware(
    AuthenticationMiddleware,
    backend=KeycardAuthBackend(auth.get_token_verifier()),
    on_error=keycard_on_error,
)

What install() adds

  • AuthenticationMiddleware with KeycardAuthBackend
  • /.well-known/oauth-protected-resource (RFC 9728)
  • /.well-known/oauth-authorization-server (RFC 8414)
  • /.well-known/jwks.json (only when WebIdentity is configured)

The middleware never gates access on its own; it just populates request.user / request.auth. Routes you do not decorate stay public.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keycardai_starlette-0.9.0.tar.gz (65.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keycardai_starlette-0.9.0-py3-none-any.whl (22.6 kB view details)

Uploaded Python 3

File details

Details for the file keycardai_starlette-0.9.0.tar.gz.

File metadata

  • Download URL: keycardai_starlette-0.9.0.tar.gz
  • Upload date:
  • Size: 65.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for keycardai_starlette-0.9.0.tar.gz
Algorithm Hash digest
SHA256 4a71301d5c1404170ca7660297c8261d1e6f6c5a2622d28a1ae2bc46d8f4c3cd
MD5 4fd4923b21d7bde7bef84d2cd0db3ecc
BLAKE2b-256 fda91efb78e7f424dd4b61a748856349a88dafaa98a25d8c480361620db287cd

See more details on using hashes here.

File details

Details for the file keycardai_starlette-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: keycardai_starlette-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 22.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for keycardai_starlette-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db590d222ce9153509415a739b0f3f9e70a0e3ab9e275f32592e4fc8c7cd0ebc
MD5 63f05a841ff097db04edb590532fa46a
BLAKE2b-256 e21376f6f913b18c9e57baf829184734abd734b14cbb418ffb5c1afe857b2331

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page