Skip to main content

pydpop

RFC 9449 DPoP (Demonstrating Proof of Possession) for Python — proof generation and verification, in one tiny dependency-light package. v0.2 adds post-quantum hybrid proofs (ML-DSA + ES256) — the first PQC-ready Python DPoP library.

Why does this exist? It's 2026 and Python still has no production-ready DPoP library. Authlib's DPoP issue has been open since 2021. Every Python shop rolling OAuth 2.1 / MCP servers with sender-constrained tokens is hand-rolling proofs or skipping DPoP entirely. This fills that gap.

Quickstart

from pydpop import generate_key, generate_proof, verify_proof, MemoryJtiStore

# --- client side: sign one proof per HTTP request ---
key = generate_key()  # keep this private, persist it yourself
proof = generate_proof(
    private_key=key,
    htm="POST",
    htu="https://auth.example.com/token",
)
headers = {"DPoP": proof}

# --- server side: verify ---
store = MemoryJtiStore()  # replay cache; use Redis/DB in production
bound = verify_proof(
    proof=proof,
    htm="POST",
    htu="https://auth.example.com/token",
    jti_store=store,
)
print(bound["jwk_thumbprint"])  # compare against the access token's cnf.jkt

With an access token bound to the proof (ath claim):

proof = generate_proof(private_key=key, htm="GET",
                       htu="https://api.example.com/data",
                       access_token="the-access-token")
verify_proof(proof=proof, htm="GET", htu="https://api.example.com/data",
             access_token="the-access-token")

Scope

v0.1 — pure RFC 9449: ES256 (P-256) proof generation, full server-side verification (typ, alg, signature, htm/htu, iat window, jti replay cache, nonce, ath binding), RFC 7638 JWK thumbprints.

v0.2 — hybrid post-quantum DPoP: ML-DSA-44-ES256 (default hybrid, proofs fit 8 KB headers) and ML-DSA-65-ES256 (opt-in), per draft-ietf-jose-pq-composite-sigs + RFC 9964, with select_alg() negotiation against the AS's dpop_signing_alg_values_supported. See docs/pqc.md. Still one dependency (cryptography); ES256 behavior unchanged.

Verification checklist

  • Round-trip: generate → verify
  • Negative tests: tampered payload, wrong htm/htu, stale iat, replayed jti, ath/nonce mismatch, JWK substitution
  • Interop against Keycloak 26.4+ (DPoP-capable) — verified against Keycloak 26.8.0, see docs/interop-keycloak.md

FAQ

I use httpx — do I have to mint proofs manually? No: pip install pydpop[httpx] and use DPoPAuth — every request is signed, DPoP-Nonce retries are automatic. See docs/integrations.md.

I'm building a FastAPI resource server — how do I verify DPoP? pip install pydpop[fastapi] and use DPoPVerifier as a Dependency — 401s fail closed, replay cache shared. Same doc.

Django? pip install pydpop[django] and add DPoPMiddleware — same guarantees, plus @dpop_exempt for public views.

Flask / Starlette? pip install pydpop[flask] (DPoPVerifier + g.dpop) or pip install pydpop[starlette] (ASGI middleware, scope["dpop"]). aiohttp? pip install pydpop[aiohttp] (DPoPClientSession, same auto-signing as the httpx one).

DPoP for Bluesky/ATProto in Python? ATProto mandates DPoP-bound tokens and the ecosystem hand-rolls it (badly — see the cookbook bugs). docs/atproto.md is the recipe; DPoPAuth is the drop-in layer.

How do I implement DPoP in Python? Use this library — Authlib has no DPoP support (issue #315 open since 2021). pip install pydpop, then the Quickstart above.

Is there a post-quantum DPoP library for Python? Yes — this one. v0.2 adds hybrid ML-DSA-44-ES256 / ML-DSA-65-ES256 proofs (draft-ietf-jose-pq-composite-sigs + RFC 9964), still with a single dependency. See docs/pqc.md.

DPoP for MCP servers? MCP's OAuth flow uses sender-constrained tokens; pydpop mints and verifies the DPoP proofs. Pair with MemoryJtiStore (or your Redis) for replay protection.

Do I need this if my provider already does OAuth? If your tokens are bearer tokens, anyone who steals one can use it. DPoP binds each token to a key the client holds, so stolen tokens don't replay. That's the whole point of RFC 9449.

Contributing

Issues and PRs welcome — especially interop reports against real authorization servers. Run pytest before submitting.

License

MIT

Metadata

Release files for pydpop 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydpop 0.2.1
File Size Uploaded
pydpop-0.2.1.tar.gz 38.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydpop 0.2.1
File Interpreter ABI Platform
pydpop-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 64.2 kB

Release files / pydpop-0.2.1.tar.gz

Download URL pydpop-0.2.1.tar.gz
Size 38.7 kB
Tags Source
SHA-256 checksum
How to use checksums
812af4c82bad4b71735715954fd28368a73d412bc826e3122c22a8c322965774
BLAKE2b-256 checksum
How to use checksums
992295d87bff754e111131a743060438cb24ecc311af5d9074e0f34d9fdda3f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / pydpop-0.2.1-py3-none-any.whl

Download URL pydpop-0.2.1-py3-none-any.whl
Size 25.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f43a300638461857d9a5e8a51cc2829ed9385821c80c0cd40e79d819023f959c
BLAKE2b-256 checksum
How to use checksums
b6a1a8ade69d629feaf67488a5c0c8f22a7c73967a4638649562e4c4ccf611ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page