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)

Related Packages

  • 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

Links

Download files

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

Source Distribution

qauth-0.1.0.tar.gz (10.3 kB view details)

Uploaded Source

Built Distribution

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

qauth-0.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file qauth-0.1.0.tar.gz.

File metadata

  • Download URL: qauth-0.1.0.tar.gz
  • Upload date:
  • Size: 10.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for qauth-0.1.0.tar.gz
Algorithm Hash digest
SHA256 073dcb9b68754d80fefb31def85a39f40dc4bfb23056e30bcf50080d15495138
MD5 678fdb588cac350922a9571371d1364a
BLAKE2b-256 972d31399e679df8b0836b924e03ac54d8bc145554662f74cd36df7ebd216307

See more details on using hashes here.

Provenance

The following attestation bundles were made for qauth-0.1.0.tar.gz:

Publisher: qauth-ci.yml on Tushar010402/Tushar-Agrawal-Website

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file qauth-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: qauth-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for qauth-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b370c4c30d2ee1c0b0c0bc7b6ce29b9c219ec56dc494b472cf0a8f7053e7e6f3
MD5 78a21af0e0243b8e901c7e35f576e058
BLAKE2b-256 e9bc694a90f1fd736a5b878f8441a706fe0b2c57dbc3f3e8a7d4586573323df1

See more details on using hashes here.

Provenance

The following attestation bundles were made for qauth-0.1.0-py3-none-any.whl:

Publisher: qauth-ci.yml on Tushar010402/Tushar-Agrawal-Website

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

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