Skip to content

Python

valiss is the Python client for the valiss scheme: it parses credentials files, issues short-lived tokens, signs HTTP and gRPC requests, and verifies them server-side without a round trip to the Go reference. It implements wire spec 1 and interchanges credentials, tokens, and signatures byte for byte with the Go and TypeScript implementations.

This guide covers installing, loading credentials, attaching credentials to httpx and requests clients, and verifying tokens and requests. It does not cover the gRPC adapters, the framework middleware (Django, ASGI), or the message-token webhook transports beyond a pointer; those live in the package README.

Install

The core (creds parsing, token issuance, request signing, verification) has one dependency, cryptography. HTTP client support is optional extras.

pip install valiss              # core
pip install 'valiss[httpx]'     # + the httpx auth hook
pip install 'valiss[requests]'  # + the requests auth hook

With uv:

uv add valiss
uv add 'valiss[httpx]'
uv add 'valiss[requests]'

Python 3.11 or newer is required. Other extras (grpc, grpcsig, django, fastapi) enable the gRPC and server-side integrations.

Load credentials

A credentials file holds a subject’s tokens plus the Ed25519 seed that signs its requests. creds.load reads and parses one:

from valiss import creds

c = creds.load("alice.creds")

creds.parse(contents) parses an in-memory string, and you can build a Creds directly from its parts. The three fields are account_token (operator-signed), user_token (account-signed), and seed:

from valiss import creds

c = creds.Creds(
    account_token=account_token,
    user_token=user_token,
    seed=user_seed,
)

Credentials without a seed are bearer credentials: c.signer() returns None and the holder cannot sign requests. The server accepts them only when the effective token is a bearer user token.

Authenticate requests with httpx

httpauth.Auth is an httpx auth hook. It attaches the credentials’ tokens and, when the credentials carry a seed, a fresh per-request signature bound to the request method, host, and path:

import httpx
from valiss import creds, httpauth

c = creds.load("alice.creds")
client = httpx.Client(auth=httpauth.Auth(c))
client.get("https://api.example.com/v1/whoami")

If the server runs a replay cache, pass nonce=True to attach a fresh per-request nonce folded into the signature:

client = httpx.Client(auth=httpauth.Auth(c, nonce=True))

Auth validates the seed at construction time, so a malformed seed fails immediately rather than on the first request.

Authenticate requests with requests

httpauth.RequestsAuth is the sibling hook for the requests library, with the same behavior and the same nonce=True flag. Set it on a session or pass it per call:

import requests
from valiss import creds, httpauth

session = requests.Session()
session.auth = httpauth.RequestsAuth(creds.load("alice.creds"))
session.get("https://api.example.com/v1/whoami")

The signature binds the host the wire actually carries: an explicit Host header wins, otherwise the URL host with a non-default port kept.

Authenticate any other client

Both hooks are thin wrappers over httpauth.credential_headers, which returns the header dict for one request. Use it with any HTTP client. The signature is bound to the method, host, and path you pass, so pass the real ones and build a fresh header set per request:

from valiss import creds, httpauth

c = creds.load("alice.creds")
headers = httpauth.credential_headers(c, "GET", "api.example.com", "/v1/whoami")

For a replay-cache server, generate a nonce and pass it through:

from valiss import token

nonce = token.new_nonce()
headers = httpauth.credential_headers(
    c, "GET", "api.example.com", "/v1/whoami", nonce=nonce,
)

Verify tokens and requests

A Python service can authenticate a request itself. The library ships the full verification chain, at parity with the Go reference.

The integrated verifier

Verifier pins the operator public key (the trust anchor) and turns the credential a transport pulled off a request into a verified Identity. It checks the account token against the operator key, expiry and activation, the allowlist (revocation), the optional user-token chain, and the request signature (or a bearer waiver), and suppresses replays:

from valiss import Verifier, Request, StaticAllowlist, MemoryReplayCache, ValissError

verifier = Verifier(
    operator_pub,                      # the pinned trust anchor
    StaticAllowlist(account_jti),      # revocation: drop the id to revoke
    replay_cache=MemoryReplayCache(),  # reject a replayed nonce
)

try:
    identity = verifier.verify(Request(
        account_token=account_token,
        user_token=user_token,
        timestamp=timestamp,
        signature=signature,
        context=context,
        nonce=nonce,
    ))
except ValissError as exc:
    ...  # exc.reason is the spec section 7 reason code

The verified Identity carries identity.account (always present), identity.user (None for account-level requests), and identity.operator. Each is a claims object with name, subject, epoch, expiry, and decoded extension claims.

Request.context is the transport’s canonical description of the request that the signature is bound to. For HTTP, httpauth.request_context(method, host, path, nonce) produces the exact bytes the client signed. The httpauth middleware and the grpcauth interceptor wrap the verifier with header extraction and status-code mapping, so a handler only ever sees an authenticated request; see the package README for those.

ALLOW_ALL accepts any account id (for development or when revocation is handled elsewhere). The allowlist is a protocol tested with in, so any container of ids works in place of StaticAllowlist, including a database-backed object that reads your own revocation store. For a service that trusts several operators, build the verifier with Verifier.with_keyring(Keyring(*operator_tokens), allowlist); the credential’s account token names the trust domain. Pass operator_token= to enforce the trust domain’s epoch and validity window, and resolver= (a callable or a {account_pub: token} mapping) to accept user-only credentials. replay_cache= likewise takes any ReplayCache implementation, so back the process-local MemoryReplayCache with Redis or a database for exactly-once suppression across instances.

Custom checks

Register application checks that run after possession is proven:

@verifier.validator
def tenant_is_active(request, identity):
    ...  # raise ValissError to reject

@verifier.extension(httpauth.Ext)
def enforce_paths(request, identity, account_ext, user_ext):
    ...  # typed HTTP extension enforcement

Verifying a single token

For tooling and tests, the token module verifies one token’s signature, type, and issuer without the full chain. These check the cryptographic binding, not expiry, the allowlist, or the trust chain:

from valiss import token

account = token.verify_account(account_token, operator_pub)  # -> AccountClaims
user = token.verify_user(user_token, account.subject)        # -> UserClaims

And to check a bare request signature against a subject public key within the skew window:

context = httpauth.request_context("GET", "api.example.com", "/v1/whoami")
token.verify_signature(user.subject, timestamp, signature, context)

Verifying a message token

A message token is a self-signed proof of origin (a webhook body, say), verified offline against the operator key. It is a proof, never a credential:

from valiss import message

claims = message.verify_message(
    proof, operator_pub,
    audience="https://api.example.com/ingest",
    payload=body,
)

verify_message walks the operator, account, user, and message chain, checks epoch agreement and every validity window, and enforces the audience and checksum bindings.

Errors

Every failure raises ValissError. Its reason attribute is the stable spec section 7 reason code (a Reason enum member), so a server can map a failure to a status code without string matching.