Skip to main content

FastAPI helpers for Supabase/JWKS bearer JWT validation and service-to-service auth forwarding.

Project description

BearerBridge

BearerBridge is a small FastAPI auth helper for teams that pass user bearer tokens across multiple services. It validates Supabase/JWKS access tokens at each service boundary and adds a simple internal service key check for service-to-service calls.

frontend -> core API -> agent API -> MCP API -> core API
           JWT + internal key all the way across trusted service hops

BearerBridge does not generate users, store secrets, or replace your identity provider. It helps your APIs consistently answer two questions:

Who is the user?        Authorization: Bearer <user jwt>
Is this our service?    X-Internal-Service-Key: <shared service secret>

Features

  • Validate bearer JWTs from Supabase or any JWKS-compatible issuer.
  • Verify issuer, audience, expiration, signature, and allowed algorithms.
  • Cache JWKS responses with automatic refresh when a new kid appears.
  • FastAPI dependencies for user JWT auth, internal service auth, or both together.
  • Constant-time internal service key comparison using secrets.compare_digest.
  • Header forwarding helper for chained service calls.
  • Framework-light core with only fastapi, httpx, and python-jose runtime dependencies.
  • MIT licensed and safe for public reuse; secrets stay in environment variables, not in the package.

Installation

pip install bearerbridge

For local development from this repo:

pip install -e .[dev]

Quick Start

from typing import Any

from fastapi import Depends, FastAPI, Request
from bearerbridge import BearerBridge, BridgeSettings

bridge = BearerBridge(
    BridgeSettings(
        jwks_url="https://PROJECT_REF.supabase.co/auth/v1/.well-known/jwks.json",
        issuer="https://PROJECT_REF.supabase.co/auth/v1",
        audience="authenticated",
        algorithms=("ES256", "RS256"),
        internal_service_key="use-a-long-random-secret-from-env",
    )
)

app = FastAPI()

@app.get("/me")
async def me(claims: dict[str, Any] = Depends(bridge.require_user)):
    return {"sub": claims.get("sub"), "email": claims.get("email")}

@app.post("/internal/run")
async def run_internal(
    claims: dict[str, Any] = Depends(bridge.require_user_and_internal_service),
):
    return {"ok": True, "user_id": claims.get("sub")}

Environment Example

BearerBridge intentionally does not read your environment by itself. Your app should load settings however it already does, then pass them into BridgeSettings.

JWKS_URL=https://PROJECT_REF.supabase.co/auth/v1/.well-known/jwks.json
JWKS_ISS=https://PROJECT_REF.supabase.co/auth/v1
JWKS_AUD=authenticated
JWKS_ALG=ES256,RS256
INTERNAL_SERVICE_KEY=generate-a-long-random-secret

Generate a service key:

python -c "import secrets; print(secrets.token_urlsafe(48))"

Use the same INTERNAL_SERVICE_KEY in services that are allowed to call each other. Do not expose it to browsers.

Forwarding Auth Headers

When one service calls the next service, forward the user JWT and add your internal service key:

import httpx
from fastapi import Request
from bearerbridge import BearerBridge, BridgeSettings

bridge = BearerBridge(BridgeSettings(...))

async def call_agent(request: Request, payload: dict):
    async with httpx.AsyncClient(timeout=20) as client:
        response = await client.post(
            "https://agent.example.com/run_tool",
            json=payload,
            headers=bridge.forward_headers(request.headers),
        )
        response.raise_for_status()
        return response.json()

forward_headers copies the inbound Authorization header and adds X-Internal-Service-Key when configured.

Common Service Chain

For a public multi-service chain, validate at every public service boundary:

React app
  -> Core API: validates user JWT
  -> Agent API: validates user JWT + internal service key
  -> MCP API: validates user JWT + internal service key
  -> Core API: validates user JWT + internal service key for internal callbacks

Public health endpoints can stay unauthenticated for load balancers. Business endpoints should require at least the user JWT, and service-only endpoints should require both JWT and internal service key.

API

BridgeSettings

BridgeSettings(
    jwks_url: str,
    issuer: str,
    audience: str | tuple[str, ...] = "authenticated",
    algorithms: tuple[str, ...] = ("ES256", "RS256"),
    jwks_ttl_seconds: int = 36000,
    internal_service_key: str | None = None,
    internal_header_name: str = "X-Internal-Service-Key",
)

BearerBridge

await bridge.decode_token(token)
await bridge.require_user(...)
await bridge.require_internal_service(...)
await bridge.require_user_and_internal_service(...)
bridge.forward_headers(inbound_headers)

Security Notes

  • Never put INTERNAL_SERVICE_KEY in frontend code.
  • Prefer HTTPS everywhere.
  • Use a long random service key and rotate it when team access changes.
  • Keep JWT validation enabled in each separately reachable service.
  • Do not trust decoded JWT claims unless signature, issuer, audience, algorithm, and expiry were verified.
  • BearerBridge validates authentication; your app still owns authorization decisions such as tenant access, roles, and row-level security.

Publishing

Build:

python -m build

Check:

python -m twine check dist/*

Publish to TestPyPI first:

python -m twine upload --repository testpypi dist/*

Publish to PyPI:

python -m twine upload dist/*

License

MIT

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

bearerbridge-0.1.0.tar.gz (6.3 kB view details)

Uploaded Source

Built Distribution

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

bearerbridge-0.1.0-py3-none-any.whl (7.9 kB view details)

Uploaded Python 3

File details

Details for the file bearerbridge-0.1.0.tar.gz.

File metadata

  • Download URL: bearerbridge-0.1.0.tar.gz
  • Upload date:
  • Size: 6.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bearerbridge-0.1.0.tar.gz
Algorithm Hash digest
SHA256 46c0c5d3ed9a701f23ad9e50430f1794a6dbf23e5ee03aa4a86f159691bac020
MD5 4a43b2d8c668a079dad4544cfdd178ab
BLAKE2b-256 c556036cd8fd1891ab1f43c53e1ccbcf270bb71f6626abbec9e82d2824e50d1c

See more details on using hashes here.

File details

Details for the file bearerbridge-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: bearerbridge-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for bearerbridge-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b73840341bef0021be6f3a8ee0b229c5697031b49273de0abec4410e43777640
MD5 40a639b047664417f092398b99d7b2d3
BLAKE2b-256 37241f1e90814eed090e7c99376bd7d5e54e57b6ad87767632039d0e5f7282e5

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