pygrindvakt

pygrindvakt is a Python binding for grindvakt 0.7.x, a runtime-agnostic Rust library for OAuth 2.0, OpenID Connect and OpenID Federation 1.1. It exposes grindvakt’s OpenID Provider engine, its Relying Party toolkit, federation trust-chain resolution, DPoP (RFC 9449) proof validation, and the JOSE / key primitives underneath to Python, including PKCS#11 (HSM) signing.

Important

OAuth 2.0 and OpenID Connect are security protocols, and this binding hands attacker-controlled request parameters, tokens and JWTs straight into authentication decisions. Before integrating, read the security guide: it covers the fail-closed guards the binding adds on top of grindvakt, the unsafe_* escape hatches, why token_url must come from configuration and never from the Host header, and why str(exc) must never be sent to a client.

What the binding adds

grindvakt itself is a library of async Rust functions and traits. The binding turns that into something a Python web application can call directly:

  • A synchronous API over an embedded tokio runtime. Every networked or store-backed call is driven to completion on a process-wide runtime with the GIL released, so threads run in parallel and the same objects keep working after fork (gunicorn, uwsgi, multiprocessing). See Stores, workers and the runtime.

  • Framework agnosticism. Requests come in as plain dicts and strings, responses go out as a pygrindvakt.http.Response (status, headers, body). The same pygrindvakt.provider.Provider works under Flask, Django, FastAPI, or anything else; the framework adapters are a few lines each (OpenID Provider on Flask, OpenID Provider on Django, OpenID Provider on FastAPI).

  • Python protocol adapters. The client store, token-use store, DPoP replay store and outbound HTTP client can each be a built-in class or any Python object implementing a small duck-typed protocol (Django ORM, redis-py, an httpx client with custom CAs). Adapters fail closed: a Python exception is logged through sys.unraisablehook and reported to the client as server_error, never as a silent pass.

  • Fail-closed hardening beyond upstream. The binding refuses several things grindvakt silently accepts: an id_token verified without a nonce, duplicate client ids, unknown Client fields, reserved id_token claims in extra_claims, and outbound redirects. Each guard is escapable only through an explicit, warning-emitting unsafe_* argument.

The binding mirrors grindvakt’s modules as Python submodules:

Submodule

Purpose

pygrindvakt.http

Framework-agnostic request / response types, the HttpClient protocol, and the built-in reqwest client.

pygrindvakt.keys

Signing keys from PEM / DER, JWK, or a PKCS#11 token; JWK generation and thumbprints.

pygrindvakt.client

Registered relying parties and the ClientStore protocol.

pygrindvakt.metadata

The OpenID Provider discovery document.

pygrindvakt.request

Parsed OIDC authorization requests.

pygrindvakt.tokens

The stateless token codec and its sealed payloads.

pygrindvakt.provider

The OpenID Provider engine and the TokenUseStore protocol.

pygrindvakt.dpop

RFC 9449 DPoP proof validation and the ReplayStore protocol.

pygrindvakt.rp

The Relying Party side: discovery, code exchange, id_token verification, userinfo, client assertions.

pygrindvakt.federation

OpenID Federation entity statements, trust-chain resolution, signed JWKS, collections, metadata policy.

pygrindvakt.discovery

Home-organization discovery and Third-Party Initiated Login.

pygrindvakt.jwt

Thin JWS sign / verify helpers.

pygrindvakt.pkce

RFC 7636 PKCE helpers.

pygrindvakt.mac

HMAC-SHA256, SHA-256, constant-time comparison.

pygrindvakt.util

Time and randomness helpers.

Design in one sentence: JSON-shaped values (claims, JWKs, metadata) cross the boundary as native Python dicts and lists, wrapped Rust objects are immutable handles you can share freely across threads, and private key material never crosses into Python at all.

Guides

Reference

Architecture decision records

The design decisions behind the binding are recorded as Markdown ADRs in the repository rather than in this manual:

  • ADR 0001: synchronous Python API over an embedded, PID-keyed tokio runtime.

  • ADR 0002: Python protocol adapters for stores and outbound HTTP, failing closed.

  • ADR 0003: fail-closed hardening added at the binding boundary.

The full list lives in the docs/adr directory.

Indices