Skip to main content

AAuth protocol implementation for Python

Project description

aauth

Python implementation of the AAuth protocol — an authorization protocol for agent-to-resource access built on HTTP Message Signatures (RFC 9421) and JWT-based proof-of-possession tokens.

See the full AAuth demo walkthrough for a live exploration of the protocol flows.

Packages

This repo contains two installable packages with distinct responsibilities:

Package pip install import as Responsibility
aauth pip install aauth import aauth Full AAuth protocol: tokens, headers, metadata, agent/resource roles
aauth-signing pip install aauth-signing from aauth_signing import ... HTTP Message Signatures (RFC 9421) + Signature-Key header — standalone, no AAuth dependency

aauth-signing is the low-level signing layer. It can be used independently if you only need RFC 9421 HTTP Message Signatures with the Signature-Key header extension (hwk, jwks_uri, jwt, jkt-jwt schemes). It has no dependency on the rest of AAuth.

aauth is the full protocol implementation. It depends on aauth-signing (pulled in automatically) and re-exports its signing functions, so you never need to import both — just import aauth and everything is available.

Installation

# Install everything (recommended)
pip install aauth

# Install only the signing layer (no tokens, headers, or agent/resource APIs)
pip install aauth-signing

For development:

pip install -e ".[dev]"

Quick Start

import aauth

# Generate an Ed25519 key pair
private_key, public_key = aauth.generate_ed25519_keypair()

# Sign a request (pseudonymous — public key embedded in header)
signed_headers = aauth.sign_request(
    method="GET",
    target_uri="https://resource.example/api/data",
    headers={},
    body=None,
    private_key=private_key,
    sig_scheme="hwk"
)

# Sign with agent identity (JWKS-backed)
signed_headers = aauth.sign_request(
    method="POST",
    target_uri="https://resource.example/api/data",
    headers={"Content-Type": "application/json"},
    body=b'{"key": "value"}',
    private_key=private_key,
    sig_scheme="jwks_uri",
    id="https://agent.example",
    kid="key-1",
    dwk="aauth-agent.json"
)

# Sign with an auth token
signed_headers = aauth.sign_request(
    method="GET",
    target_uri="https://resource.example/api/data",
    headers={},
    body=None,
    private_key=private_key,
    sig_scheme="jwt",
    jwt=auth_token
)

Library Reference

Key Management

import aauth

private_key, public_key = aauth.generate_ed25519_keypair()
jwk = aauth.public_key_to_jwk(public_key, kid="key-1")
thumbprint = aauth.calculate_jwk_thumbprint(jwk)

Signature Verification

is_valid = aauth.verify_signature(
    method=request.method,
    target_uri=str(request.url),
    headers=dict(request.headers),
    body=request_body,
    signature_input_header=request.headers.get("Signature-Input"),
    signature_header=request.headers.get("Signature"),
    signature_key_header=request.headers.get("Signature-Key"),
    jwks_fetcher=my_jwks_fetcher  # required for jwks_uri/jwt schemes
)

What the signature covers by default: @method, @authority, @path, signature-key (plus @query when a query string is present). This is enough to bind the signature to the specific endpoint and prevent replay across methods or hosts.

Body signing is opt-in and usually not needed. Resources can require content-digest and/or content-type coverage via additional_signature_components, but this adds significant complexity — bodies must be fully buffered before signing/verifying, content-encoding negotiation interferes, and most agent-to-resource interactions are already protected by TLS. Covering the request line and key identity is sufficient for the vast majority of use cases.

Token Creation

# Resource token (resource → auth server)
resource_token = aauth.create_resource_token(
    iss="https://resource.example",
    aud="https://auth.example",
    agent="https://agent.example",
    agent_jkt=agent_thumbprint,
    scope="data.read data.write",
    private_key=resource_private_key,
    kid="resource-key-1"
)

# Auth token (auth server → agent)
auth_token = aauth.create_auth_token(
    iss="https://auth.example",
    aud="https://resource.example",
    agent="https://agent.example",
    cnf_jwk=agent_jwk,
    act={"sub": "https://agent.example"},
    scope="data.read",
    private_key=auth_private_key,
    kid="auth-key-1"
)

# Parse token claims (no verification)
claims = aauth.parse_token_claims(token)

AAuth Header Parsing

# Parse an AAuth challenge from a resource's 401 response
challenge = aauth.parse_agent_auth_header(
    'requirement=auth-token; resource-token="..."; auth-server="https://auth.example"'
)

# Build an AAuth challenge
challenge_header = aauth.build_agent_auth_challenge(
    require_signature=True,
    require_identity=True,
    require_auth_token=True,
    resource_token=resource_token,
    auth_server="https://auth.example"
)

High-Level Agent and Resource APIs

# Agent-side request signer
signer = aauth.AgentRequestSigner(
    private_key=private_key,
    agent_id="https://agent.example",
    agent_token=agent_token
)

signed_headers = signer.sign_request(
    method="GET",
    target_uri="https://resource.example/api/data",
    headers={},
    body=None,
    sig_scheme="jwt"
)

# Resource-side request verifier
verifier = aauth.RequestVerifier(
    canonical_authorities=["resource.example:443"],
    jwks_fetcher=my_jwks_fetcher
)

result = verifier.verify_request(
    method="GET",
    target_uri="https://resource.example/api/data",
    headers=request_headers,
    body=request_body,
    require_identity=True,
    require_auth_token=True
)

if result["valid"]:
    print(f"Agent: {result['agent_id']}, Scopes: {result['scopes']}")

Package Structure

aauth-signing/          ← standalone pip package (aauth_signing)
└── aauth_signing/
    ├── signer.py       # sign_request — builds Signature-Input/Signature/Signature-Key
    ├── verifier.py     # verify_signature — validates RFC 9421 signatures
    ├── signature_key.py# Signature-Key header (hwk/jwks_uri/jwt/jkt-jwt schemes)
    ├── signature_base.py
    ├── signature_input.py
    ├── signature.py
    ├── algorithms.py
    └── keys/           # JWK helpers used by the signing layer

aauth/                  ← main pip package (aauth), depends on aauth-signing
├── signing/            # Thin shims — re-exports aauth_signing.{signer,verifier,...}
├── keys/               # Key management and JWK operations
├── tokens/             # JWT token creation and validation (resource, auth, agent tokens)
├── headers/            # AAuth header parsing and building (AAuth-Requirement, etc.)
├── metadata/           # Metadata discovery (.well-known endpoints)
├── http/               # Deferred response helpers (202 + polling)
├── agent/              # Agent role: signing, challenge handling, token exchange, polling
└── resource/           # Resource role: challenge building, request verification

Testing

pytest tests/ -v

Protocol

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aauth-0.3.5.tar.gz (48.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aauth-0.3.5-py3-none-any.whl (51.1 kB view details)

Uploaded Python 3

File details

Details for the file aauth-0.3.5.tar.gz.

File metadata

  • Download URL: aauth-0.3.5.tar.gz
  • Upload date:
  • Size: 48.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for aauth-0.3.5.tar.gz
Algorithm Hash digest
SHA256 da955ab49cf771144b575e82665f060c684ed262a74af108ad648c560686b044
MD5 530bc19638ca01ac51a3454855556e81
BLAKE2b-256 0e30381ecde766cf726eb4c82be1cd0cd34e8c44600332dc2c0cb02525c7aaef

See more details on using hashes here.

File details

Details for the file aauth-0.3.5-py3-none-any.whl.

File metadata

  • Download URL: aauth-0.3.5-py3-none-any.whl
  • Upload date:
  • Size: 51.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for aauth-0.3.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8a0e313377bbf090640063c53aa488cf2959d0ef9be11377400d69ef8f97eff7
MD5 099adbf422e87e45b6f9ede5020bf46a
BLAKE2b-256 0eac9ca18701703d98f3c19c14a1ea1d8007d701ffafe7f8969351de45254045

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page