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 hookWith 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 codeThe 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 enforcementVerifying 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) # -> UserClaimsAnd 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.