This release is a pre-release and may not be stable for production use.
Swarmauri Tokens DPoP Bound JWT
DPoP-bound JSON Web Token (JWT) services for Swarmauri implementing RFC 9449 DPoP proof binding and RFC 7638 JWK thumbprints.
Features
- Mints and verifies DPoP-bound JWT access tokens using the algorithms provided by the base
JWTTokenService(HS256,RS256,PS256,ES256,EdDSA). - Automatically injects the RFC 7638 thumbprint into the
cnf.jktclaim when the DPoP context exposes the caller's public JWK. - Enforces DPoP proof validation by checking the
dpop+jwtheader type, verifying the proof signature against the embedded JWK, and binding the HTTP method/URI,iat, and optional nonce (proof_max_age_scontrols the allowed clock skew). - Accepts an optional
replay_check(jti)callback to harden against proof re-use and adpop_ctx_getterto integrate with request-scoped metadata providers. - Compatible with the
JWTTokenServicesurface for issuer, subject, audience, scope, key selection, and header overrides.
Installation
pip
pip install swarmauri_tokens_dpopboundjwt
uv
uv add swarmauri_tokens_dpopboundjwt
Poetry
poetry add swarmauri_tokens_dpopboundjwt
Usage
DPoP context expectations
DPoPBoundJWTTokenService extends JWTTokenService and requires request context in order to bind and verify DPoP proofs. Supply a callable via dpop_ctx_getter that returns a mapping with the following keys:
jwk: optional public JWK exposed during minting to automatically populatecnf.jkt.proof: the DPoP proof JWT received with a request.htm: HTTP method used for the protected resource request.htu: absolute HTTP URI for the protected resource request.nonce: optional server-provided nonce that, when present, must match the proof.
Set enforce_proof=False only when you intentionally want to accept tokens without a proof (for example during incremental rollout).
Example: mint and verify a DPoP-bound JWT
# README example: mint and verify a DPoP-bound JWT
import asyncio
import json
import os
import time
import uuid
from typing import Any, Iterable, Mapping, Optional
import jwt
from cryptography.hazmat.primitives.asymmetric import ec
from jwt import algorithms
from swarmauri_core.crypto.types import (
ExportPolicy,
JWAAlg,
KeyRef,
KeyType,
KeyUse,
)
from swarmauri_core.key_providers.IKeyProvider import IKeyProvider
from swarmauri_tokens_dpopboundjwt import DPoPBoundJWTTokenService
class StaticKeyProvider(IKeyProvider):
"""Minimal symmetric key provider suitable for examples/tests."""
def __init__(self, secret: bytes, kid: str = "default") -> None:
self._kid = kid
self._secret = secret
self._ref = KeyRef(
kid=kid,
version=1,
type=KeyType.SYMMETRIC,
uses=(KeyUse.SIGN, KeyUse.VERIFY),
export_policy=ExportPolicy.SECRET_WHEN_ALLOWED,
material=secret,
)
def supports(self) -> Mapping[str, Iterable[str]]:
return {"algs": ("HS256",)}
async def create_key(self, spec: Any): # pragma: no cover - unused in example
raise NotImplementedError
async def import_key(
self, spec: Any, material: bytes, *, public: bytes | None = None
): # pragma: no cover - unused in example
raise NotImplementedError
async def rotate_key(
self, kid: str, *, spec_overrides: Optional[dict] = None
): # pragma: no cover - unused in example
raise NotImplementedError
async def destroy_key(
self, kid: str, version: Optional[int] = None
) -> bool: # pragma: no cover - unused in example
return False
async def get_key(
self, kid: str, version: Optional[int] = None, *, include_secret: bool = False
) -> KeyRef:
return self._ref
async def list_versions(self, kid: str) -> tuple[int, ...]: # pragma: no cover
return (self._ref.version,)
async def get_public_jwk( # pragma: no cover - unused in example
self, kid: str, version: Optional[int] = None
) -> dict:
raise NotImplementedError
async def jwks( # pragma: no cover - unused in example
self, *, prefix_kids: Optional[str] = None
) -> dict:
raise NotImplementedError
async def random_bytes(self, n: int) -> bytes: # pragma: no cover - unused
return os.urandom(n)
async def hkdf( # pragma: no cover - unused in example
self, ikm: bytes, *, salt: bytes, info: bytes, length: int
) -> bytes:
raise NotImplementedError
async def main() -> None:
key_provider = StaticKeyProvider(b"super-secret-signing-key")
request_context: dict[str, object] = {}
def get_ctx() -> dict[str, object]:
return request_context
service = DPoPBoundJWTTokenService(
key_provider,
default_issuer="https://issuer.test",
dpop_ctx_getter=get_ctx,
proof_max_age_s=300,
)
# Client presents the public key when the token is minted so we can bind cnf.jkt
dpop_private_key = ec.generate_private_key(ec.SECP256R1())
jwk_public = json.loads(algorithms.ECAlgorithm.to_jwk(dpop_private_key.public_key()))
request_context.update({"jwk": jwk_public})
access_token = await service.mint(
{"sub": "alice@example.com"},
alg=JWAAlg.HS256,
audience="https://api.example.test",
scope="read:messages",
)
# Incoming request carries a DPoP proof signed with the same key material
htu = "https://api.example.test/resource"
htm = "GET"
nonce = "server-provided-nonce"
proof_claims = {
"htu": htu,
"htm": htm,
"iat": int(time.time()),
"jti": str(uuid.uuid4()),
"nonce": nonce,
}
proof_headers = {"typ": "dpop+jwt", "jwk": jwk_public}
proof_jwt = jwt.encode(
proof_claims, dpop_private_key, algorithm="ES256", headers=proof_headers
)
request_context.update(
{
"proof": proof_jwt,
"htu": htu,
"htm": htm,
"nonce": nonce,
}
)
verified_claims = await service.verify(
access_token, audience="https://api.example.test"
)
print("Verified subject:", verified_claims["sub"])
print("Bound JWK thumbprint:", verified_claims["cnf"]["jkt"])
if __name__ == "__main__":
asyncio.run(main())
Entry Point
The service registers under the swarmauri.tokens entry point as DPoPBoundJWTTokenService.
A plain JWTTokenService is also exported for cases where DPoP binding is not required.
Want to help?
If you want to contribute to swarmauri-sdk, read up on our guidelines for contributing that will help you get started.
Metadata
Release files for swarmauri_tokens_dpopboundjwt 0.11.0.dev1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| swarmauri_tokens_dpopboundjwt-0.11.0.dev1.tar.gz | 10.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| swarmauri_tokens_dpopboundjwt-0.11.0.dev1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 23.9 kB
Release files / swarmauri_tokens_dpopboundjwt-0.11.0.dev1.tar.gz
| Download URL | swarmauri_tokens_dpopboundjwt-0.11.0.dev1.tar.gz |
|---|---|
| Size | 10.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
225755fdba45498f4b23b73c640b07972a92db3cedf8150b08fbebf6e70a797f
|
|
BLAKE2b-256 checksum How to use checksums |
8eb100f28d7d2f040e6afb782b43e43b6c89340a53c515e41ec3dd1569fcaa4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / swarmauri_tokens_dpopboundjwt-0.11.0.dev1-py3-none-any.whl
| Download URL | swarmauri_tokens_dpopboundjwt-0.11.0.dev1-py3-none-any.whl |
|---|---|
| Size | 13.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c9817142ad348e673526c909f7324a0e47cf691eae6746a2586fbb33e0ac5a82
|
|
BLAKE2b-256 checksum How to use checksums |
52947e8da6ab888f302bc8f789b3541cf3da6168ae5320ede1007a89dfc6b53c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|