Skip to content

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.

pip install "pyjwt[crypto]"
fastapi_verify_token.py
"""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.

pip install requests
python_client_get_token.py
"""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.