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
kidappears. - 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, andpython-joseruntime 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_KEYin 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46c0c5d3ed9a701f23ad9e50430f1794a6dbf23e5ee03aa4a86f159691bac020
|
|
| MD5 |
4a43b2d8c668a079dad4544cfdd178ab
|
|
| BLAKE2b-256 |
c556036cd8fd1891ab1f43c53e1ccbcf270bb71f6626abbec9e82d2824e50d1c
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b73840341bef0021be6f3a8ee0b229c5697031b49273de0abec4410e43777640
|
|
| MD5 |
40a639b047664417f092398b99d7b2d3
|
|
| BLAKE2b-256 |
37241f1e90814eed090e7c99376bd7d5e54e57b6ad87767632039d0e5f7282e5
|