Skip to content

sdk/python

Async Python server SDK for networkless JWT verification, request authentication, and webhook signature validation.

Status

Implemented and verified locally. Real IdP round-trip verification (JWKS fetch, token sign/verify against a live XID instance) has not been performed yet and must be completed before production use.

Registry status: UNPUBLISHED. Install this SDK only from the repository source checkout; do not use an external package registry.

Request authentication is Bearer-only by default. An application-owned JWT cookie is read only when its exact name is configured. The opaque __Host-xid.rt.* Core cookie is never scanned or verified locally; exchange it by forwarding the complete Cookie header to exact same-origin POST /v1/sessions/token with redirects disabled, and accept only a response containing the token field.

Install

pip install "xid @ git+https://github.com/StringKe/xid#subdirectory=sdk/python"

Quick start

Construct one XidClient at startup and reuse it. The client caches JWKS internally.

from xid import XidClient

client = XidClient(
    issuer="https://xid.dev",
    audience="https://api.yourapp.com",  # optional
)

# Verify a token
claims = await client.verify_token("eyJ...")
print(claims.sub, claims.email, claims.scope)

# Authenticate a request (Bearer-only by default)
status = await client.authenticate_request(headers=dict(request.headers))
if not status.authenticated:
    raise Unauthorized()
user_id = status.claims.sub

# Explicit same-origin Core session -> JWT exchange
token = await client.exchange_session_token(
    incoming_request_url="https://app.example.com/account",
    cookie_header=request.headers["cookie"],
)

Verify webhook

from xid import WebhookVerificationError

try:
    webhook = client.verify_webhook(
        payload=request.body,
        headers=dict(request.headers),
        secret="whsec_xxx",
    )
    import json
    event = json.loads(webhook.body)
except WebhookVerificationError as exc:
    raise BadRequest(str(exc))

FastAPI integration

from fastapi import FastAPI, Depends, HTTPException, Request
from xid import XidClient, TokenClaims

app = FastAPI()
xid = XidClient(issuer="https://xid.dev")

@app.on_event("shutdown")
async def shutdown():
    await xid.aclose()

async def require_auth(request: Request) -> TokenClaims:
    status = await xid.authenticate_request(dict(request.headers))
    if not status.authenticated:
        raise HTTPException(status_code=401)
    return status.claims

@app.get("/me")
async def me(claims: TokenClaims = Depends(require_auth)):
    return {"sub": claims.sub, "email": claims.email}

XidClient options

Parameter Default Description
issuer required XID issuer URL
audience None Expected aud claim; None skips validation
jwks_ttl 3600 JWKS in-memory cache TTL in seconds
http_timeout 10.0 JWKS fetch timeout in seconds
cookie_name disabled Application-owned JWT cookie name; disabled unless explicitly configured
leeway 0 Clock skew tolerance in seconds

Core API

Method Description
await client.verify_token(token) Verify JWT string; raises TokenVerificationError on failure.
await client.authenticate_request(headers, cookies) Extract and verify token from headers/cookies. Returns AuthStatus; does not raise.
client.verify_webhook(payload, headers, secret) Synchronous. Validates svix HMAC-SHA256 + 5-minute replay window. Raises WebhookVerificationError on failure.
await client.aclose() Release underlying HTTP client resources.

Platform notes

  • Async-first. Sync callers (Django/Flask) can wrap with asyncio.run().
  • Depends on pyjwt[crypto] >=2.8 and httpx >=0.27. Python 3.10+ required.
  • Multi-worker deployments share no JWKS cache across processes. A shared cache (Redis) is a planned improvement.
Navigation

Type to search...

Use arrow keys to navigateEnter to selectEscape to close