Python¶
requests FastAPI PyJWT
Verifying a token (FastAPI)¶
Use this in a backend API that should only serve requests carrying a valid Obsidian-issued token — for example, "one of my own devices is allowed to hit this endpoint."
The verification is entirely local: PyJWKClient fetches Obsidian's public
signing key once and caches it, re-fetching only if it encounters a kid
it hasn't seen before. There is no network call to Obsidian on the request
path.
"""Drop this into any other FastAPI project to require a valid Obsidian
access token on a route. Verifies the JWT's signature against Obsidian's
JWKS locally — no network call back to Obsidian on every request.
Install: pip install "pyjwt[crypto]"
Usage:
from fastapi_verify_token import require_device
@app.get("/reports")
def reports(device_id: str = Depends(require_device)):
...
"""
import jwt
from fastapi import Header, HTTPException
from jwt import PyJWKClient
OBSIDIAN_ISSUER = "https://auth.jyjwong.com" # set to your Obsidian deployment — checked against the JWT's `iss` claim
OBSIDIAN_JWKS_URL = "https://jwks.jyjwong.com/.well-known/jwks.json" # set to your Obsidian deployment's public JWKS domain (terraform output jwks_url) — deliberately NOT under OBSIDIAN_ISSUER, which requires a client certificate
# PyJWKClient caches keys in-memory and only re-fetches the JWKS when it sees
# an unrecognized `kid` — cheap enough to construct once at import time.
_jwk_client = PyJWKClient(OBSIDIAN_JWKS_URL)
def require_device(authorization: str = Header(...)) -> str:
"""FastAPI dependency: returns the authenticated device_id, or raises 401."""
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = authorization.removeprefix("Bearer ")
try:
signing_key = _jwk_client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
issuer=OBSIDIAN_ISSUER,
options={"require": ["exp", "iat", "sub", "iss"]},
)
except jwt.PyJWTError as exc:
raise HTTPException(status_code=401, detail=f"Invalid token: {exc}") from exc
return payload["sub"] # device_id
Wire it into a route as a dependency:
from fastapi_verify_token import require_device
@app.get("/reports")
def reports(device_id: str = Depends(require_device)):
...
require_device returns the authenticated device_id (the JWT's sub
claim) on success, or raises HTTPException(401) if the token is missing,
expired, or fails signature/issuer verification.
Fetch JWKS from the JWKS domain, not the issuer
OBSIDIAN_JWKS_URL must point at the separate, no-mTLS JWKS domain
(jwks.jyjwong.com), never at a path under OBSIDIAN_ISSUER. The
issuer's domain requires a client certificate on every route — a
resource server with no device cert of its own gets a TLS-level
connection reset trying to fetch JWKS from there.
Not using FastAPI? The same two-domain split — JWKS from the public
domain, iss checked against the mTLS domain — works with any language's
standard JWT library.
Getting a token (server-to-server or CLI)¶
Use this when the caller itself is one of your provisioned devices — a cron job, a CLI tool, or one service calling another — and can present its own client certificate directly. There's no browser involved, so no certificate-picker UX to worry about.
"""For server-to-server or CLI callers (no browser cert picker involved —
the client cert is passed explicitly, so this just works non-interactively).
Install: pip install requests
"""
import requests
OBSIDIAN_ISSUER = "https://auth.jyjwong.com" # set to your Obsidian deployment
DEVICE_CERT = ("devices/my-server/my-server.crt", "devices/my-server/my-server.key")
def get_access_token() -> str:
response = requests.post(f"{OBSIDIAN_ISSUER}/auth/token", cert=DEVICE_CERT)
response.raise_for_status()
return response.json()["access_token"]
def call_protected_api():
token = get_access_token()
response = requests.get(
"https://api.jyjwong.com/reports",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
return response.json()
Point DEVICE_CERT at the key/cert pair written by
scripts/provision_device.py
for this device, then call get_access_token() before hitting your other
API.