Skip to main content

QAuth - Post-Quantum Authentication for Python

PyPI version Python 3.9+ License: MIT

Next-generation authentication protocol designed to replace OAuth 2.0 and JWT with post-quantum security.

Installation

# Using pip
pip install qauth

# Using pip3
pip3 install qauth

# Using pipx (for CLI tools)
pipx install qauth

# Using poetry
poetry add qauth

# Using pdm
pdm add qauth

# Using uv
uv pip install qauth

Quick Start

from qauth import QAuthServer, QAuthClient, PolicyEngine

# Create a server instance
server = QAuthServer(
    issuer="https://auth.example.com",
    audience="https://api.example.com"
)

# Create an access token
token = server.create_token(
    subject="user-123",
    policy_ref="urn:qauth:policy:default",
    validity_seconds=3600,
    claims={
        "email": "user@example.com",
        "roles": ["user", "premium"],
    },
)

# Validate a token
payload = server.validate_token(token)
print(f"Subject: {payload.sub.decode()}")
print(f"Expires: {payload.exp}")

Client-Side Usage

from qauth import QAuthClient

# Create a client instance (generates a new keypair)
client = QAuthClient()

# Get the client's public key (send to server during auth)
public_key = client.public_key

# Create proof of possession for API requests
proof = client.create_proof("GET", "/api/resource", token)

# Make API request with token and proof
import requests
response = requests.get(
    "https://api.example.com/resource",
    headers={
        "Authorization": f"QAuth {token}",
        "X-QAuth-Proof": proof,
    },
)

Server-Side Validation

from qauth import QAuthValidator, ProofValidator

# Create a validator with pre-shared issuer keys
validator = QAuthValidator(
    keys=issuer_keys,
    issuer="https://auth.example.com",
    audience="https://api.example.com",
)

# Validate token
try:
    payload = validator.validate(token)
    print(f"Token valid for user: {payload.sub}")
except Exception as e:
    print(f"Token validation failed: {e}")

# Validate proof of possession
proof_validator = ProofValidator(client_public_key)
try:
    proof_validator.validate(proof, "GET", "/api/resource", token)
    print("Proof valid")
except Exception as e:
    print(f"Proof validation failed: {e}")

Policy-Based Authorization

from qauth import PolicyEngine, Effect

engine = PolicyEngine()

# Load a policy
engine.load_policy({
    "id": "urn:qauth:policy:api-access",
    "version": "2026-01-30",
    "issuer": "https://auth.example.com",
    "rules": [
        {
            "id": "read-projects",
            "effect": "allow",
            "resources": ["projects/*"],
            "actions": ["read", "list"],
        },
        {
            "id": "admin-only",
            "effect": "allow",
            "resources": ["admin/**"],
            "actions": ["*"],
            "conditions": {
                "custom": {
                    "role": {"in": ["admin"]},
                },
            },
        },
    ],
})

# Evaluate authorization
result = engine.evaluate(
    "urn:qauth:policy:api-access",
    {
        "subject": {
            "id": "user-123",
            "attributes": {"role": "user"},
        },
        "resource": {
            "path": "projects/456",
        },
        "request": {
            "action": "read",
        },
    },
)

if result.effect == Effect.ALLOW:
    print("Access granted")
else:
    print(f"Access denied: {result.reason}")

FastAPI Integration

from fastapi import FastAPI, Depends, HTTPException, Header
from qauth import QAuthValidator, ProofValidator, IssuerKeys

app = FastAPI()

# Configure validator
validator = QAuthValidator(
    keys=issuer_keys,
    issuer="https://auth.example.com",
    audience="https://api.example.com",
)

async def verify_qauth(
    authorization: str = Header(...),
    x_qauth_proof: str = Header(...),
):
    if not authorization.startswith("QAuth "):
        raise HTTPException(401, "Invalid authorization header")

    token = authorization[6:]

    try:
        payload = validator.validate(token)
    except Exception as e:
        raise HTTPException(401, f"Invalid token: {e}")

    # In production, also verify the proof

    return payload

@app.get("/api/resource")
async def get_resource(payload = Depends(verify_qauth)):
    return {"user": payload.sub.decode()}

API Reference

QAuthServer

Server-side class for token creation and validation.

server = QAuthServer(issuer: str, audience: str)

# Get public keys for sharing with validators
keys = server.get_public_keys() -> IssuerKeys

# Create a token
token = server.create_token(
    subject: str | bytes,
    policy_ref: str,
    audience: str | list[str] | None = None,
    validity_seconds: int = 3600,
    client_key: bytes | None = None,
    device_key: bytes | None = None,
    claims: dict[str, Any] | None = None,
) -> str

# Validate a token
payload = server.validate_token(token: str) -> TokenPayload

QAuthClient

Client-side class for proof of possession.

client = QAuthClient()

# Get client's public key
public_key = client.public_key -> bytes

# Create proof for API request
proof = client.create_proof(
    method: str,
    uri: str,
    token: str,
    body: bytes | None = None,
) -> str

PolicyEngine

Evaluate authorization policies.

engine = PolicyEngine()

# Load a policy
engine.load_policy(policy: dict[str, Any]) -> None

# Evaluate authorization
result = engine.evaluate(
    policy_id: str,
    context: dict[str, Any],
) -> EvaluationResult

Data Classes

@dataclass
class TokenPayload:
    sub: bytes        # Subject identifier
    iss: str          # Issuer
    aud: list[str]    # Audiences
    exp: int          # Expiration time
    iat: int          # Issued at
    nbf: int          # Not before
    jti: bytes        # Token ID
    rid: bytes        # Revocation ID
    pol: str          # Policy reference
    ctx: bytes        # Context hash
    cst: dict         # Custom claims

@dataclass
class EvaluationResult:
    effect: Effect
    matched_rule: str | None
    reason: str

class Effect(Enum):
    ALLOW = "allow"
    DENY = "deny"

Requirements

  • Python 3.9+
  • cryptography >= 41.0.0
  • pynacl >= 1.5.0

Why QAuth over JWT?

JWT/OAuth Problem QAuth Solution
Algorithm confusion attacks Server-enforced, no client selection
Bearer tokens can be stolen Proof-of-possession mandatory
No built-in revocation Instant revocation system
Payload visible (base64) Encrypted with XChaCha20-Poly1305
Single signature Dual: Ed25519 + ML-DSA-65
No post-quantum security ML-DSA-65 (NIST FIPS 204)
  • Rust: cargo add qauth
  • TypeScript/Node.js: npm install @qauth/sdk
  • Go: go get github.com/tushar-agrawal/qauth

License

MIT License - LICENSE

Author

Tushar Agrawal - tusharagrawal.in

Metadata

Release files for qauth 0.1.0

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

Source distribution (sdist)

Source distribution for qauth 0.1.0
File Size Uploaded
qauth-0.1.0.tar.gz 10.3 kB Details

Built distribution (wheel)

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

Total release size: 17.4 kB

Release files / qauth-0.1.0.tar.gz

Download URL qauth-0.1.0.tar.gz
Size 10.3 kB
Tags Source
SHA-256 checksum
How to use checksums
073dcb9b68754d80fefb31def85a39f40dc4bfb23056e30bcf50080d15495138
BLAKE2b-256 checksum
How to use checksums
972d31399e679df8b0836b924e03ac54d8bc145554662f74cd36df7ebd216307
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Jan 30, 2026.

Transparency log

Release files / qauth-0.1.0-py3-none-any.whl

Download URL qauth-0.1.0-py3-none-any.whl
Size 7.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b370c4c30d2ee1c0b0c0bc7b6ce29b9c219ec56dc494b472cf0a8f7053e7e6f3
BLAKE2b-256 checksum
How to use checksums
e9bc694a90f1fd736a5b878f8441a706fe0b2c57dbc3f3e8a7d4586573323df1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Jan 30, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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