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.8andhttpx >=0.27. Python 3.10+ required. - Multi-worker deployments share no JWKS cache across processes. A shared cache (Redis) is a planned improvement.