valiss

package module
v0.13.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 15 Imported by: 0

README

valiss

VALidator-ISSuer: decentralized tenant authentication for Go services, gRPC and HTTP, built on a three-level chain of Ed25519 keys: operator → account → user — with an optional fourth level of per-message proof-of-origin tokens.

Most multi-tenant services end up with a central auth dependency: an OAuth provider, a session store, a per-tenant key registry — something every request has to consult and every deployment has to keep alive. valiss inverts that. Trust is a single public key baked into the server; everything else is self-contained signed credentials that verify offline. There is no auth service to run, no token introspection endpoint to call, and issuing credentials never touches production infrastructure.

Where it fits:

  • Multi-tenant APIs. Each customer is an account with its own keypair. You sign one account credential per customer; they mint their own user and service credentials from it, scoped down as they see fit, without asking you.
  • Machine-to-machine auth. Services and agents authenticate with keys and per-request signatures — no shared secrets, no password rotation, and a stolen token is useless without the key that signs requests.
  • Edge and on-prem. Verifiers need only the operator public key (plus an allowlist and, optionally, account tokens in static config), so isolated deployments authenticate the same way as connected ones.
  • Browsers and other weak clients. Short-lived bearer user tokens cover clients that cannot hold a key.

How it works, in one paragraph: an operator key signs account (tenant) tokens; an account key signs user tokens; every request is signed by the subject's own key over a timestamp. The server verifies the chain against the pinned operator key, checks the account token against a revocation allowlist, and hands the tenant (and user) identity to the handler. Delegation is real: revoking one account cuts off everything under it, and an account can never grant more than it holds.

Key types map to nkeys directly: operator SO.../O..., account SA.../A..., user SU.../U.... Tokens are valiss's own typed claims in an nkey-signed JWT: sub is the subject's public key, name an optional human-readable label (WithName; an unnamed entity is represented by its public key), validity via optional absolute exp/nbf (absent exp = never expires). Names are issuer-asserted and nowhere checked for uniqueness at issuance: collections holding several entities side by side own the uniqueness of names in their scope.

Rotation and mass revocation

The operator can publish a self-signed operator token: a policy statement over the trust domain, carrying an epoch counter and an optional validity window. Verifiers configured with it accept only account and user tokens stamped with the current epoch:

opTok, _ := valiss.IssueOperator(operator, valiss.WithName("prod-us"), valiss.WithEpoch(3))
acct, _  := valiss.IssueAccount(operator, acctPub, valiss.WithName("acme"), valiss.WithEpoch(3), ...)

verifier := valiss.NewVerifier(operatorPub, allowlist,
    valiss.WithOperatorToken(opTok))

Bumping the epoch and re-minting rotates the whole domain: every token from earlier epochs is rejected cryptographically, no allowlist edits. The operator token's own exp bounds the entire domain, forcing a periodic rotation ceremony. The pinned public key remains the trust anchor — the operator token only carries policy signed by it. Selective revocation stays with the allowlist; the two levers are complementary.

Message tokens

A service that emits artifacts to third parties — webhooks, queue messages, exported documents — can extend the chain one level further with message tokens: a short-lived JWT minted with a user key, one per emitted message, that any receiver can verify offline knowing only the published operator public key. The token binds the destination (aud), the exact payload bytes (a SHA-256 checksum claim), a short validity window, and the trust-domain epoch:

// Emitter: holds the user seed and its chain tokens.
payload := renderWebhook(event)
tok, _ := valiss.IssueMessage(userKP,
    valiss.WithAudience("https://receiver.example/hook"),
    valiss.WithChecksum(valiss.Checksum(payload)),
    valiss.WithTTL(30*time.Second),
    valiss.WithEpoch(epoch),
    valiss.WithChain(accountToken, userToken),
)

// Receiver: knows only the operator public key.
claims, err := valiss.VerifyMessage(tok, operatorPub,
    valiss.ExpectAudience("https://receiver.example/hook"),
    valiss.WithPayload(receivedBody),
)
// claims.Account.Name / claims.User.Name identify the emitter.

Verification walks the full chain operator → account → user → message and requires every level to agree on the epoch; WithOperatorPolicy(opTok) additionally enforces the current domain epoch and the operator token's own window. ExpectAudience is the lever against cross-destination replay, WithPayload against payload tampering — receivers should set both. Stored messages verify after token expiry with valiss.At(receivedAt), which evaluates all windows and policy as of that instant.

The chain travels either embedded (WithChain at mint — the token is fully self-contained at the cost of roughly three tokens of size) or out-of-band (WithChainTokens on VerifyMessage — smaller tokens, but the receiver must be handed the chain separately). A token that embeds a chain must match any supplied one.

Message tokens are proofs, not credentials. Possession of one grants nothing: the request Verifier never accepts them, and receivers must not treat them as bearer credentials. Offline receivers hold no allowlist; an online receiver that wants revocation checks claims.Account.ID against its own allowlist.

Two contrib packages wire this up end to end. contrib/httpsig: a client RoundTripper that mints a token per outgoing request (audience = host + path, checksum over the body) and a middleware that verifies it:

// Emitter (bundle creds: account token + user token + user seed).
transport, _ := httpsig.NewTransport(c, nil)
client := &http.Client{Transport: transport}

// Receiver.
mw := httpsig.NewMiddleware(operatorPub)
srv := &http.Server{Handler: mw(hookHandler)}
// in the handler: claims, ok := valiss.MessageFromContext(r.Context())

Gin and Echo receivers use the native middleware over the same core: ginsig.NewMiddleware(operatorPub) / echosig.NewMiddleware(operatorPub), claims via ginsig.MessageFrom(c) / echosig.MessageFrom(c).

contrib/grpcsig: unary interceptors on both ends (audience = full method, checksum over the request message's deterministic protobuf encoding — keep the protobuf runtime versions of emitter and receiver in step):

ci, _ := grpcsig.UnaryClientInterceptor(c)
conn, _ := grpc.NewClient(addr, grpc.WithUnaryInterceptor(ci), ...)

srv := grpc.NewServer(grpc.UnaryInterceptor(
    grpcsig.UnaryServerInterceptor(operatorPub)))

The transports pin the audience and payload bindings themselves; extra valiss.VerifyMessageOptions (e.g. WithOperatorPolicy) pass through each package's WithVerifyOptions. The mint TTL defaults to valiss.DefaultMessageTTL (30s), overridable with each package's WithTTL.

Embedding the chain costs ~1.4 KB per message. High-volume emitters opt into chain negotiation instead: the emitter sends bare tokens (~650 B), and a receiver that does not know the chain answers valiss-chain: required (response header on HTTP, trailer on gRPC), making the transport retransmit once with the chain in detached headers. With a chain cache on the receiver the retransmit happens once per emitter, not per message, and a domain rotation self-heals: the stale cached chain is evicted and re-negotiated on the next message.

// Emitter: bare tokens, chain on demand.
transport, _ := httpsig.NewTransport(c, nil, httpsig.WithChainNegotiation())
// gRPC: grpcsig.UnaryClientInterceptor(c, grpcsig.WithChainNegotiation())

// Receiver: remember negotiated chains between messages.
mw := httpsig.NewMiddleware(operatorPub,
    httpsig.WithChainCache(valiss.NewMemoryChainCache()))
// gRPC: grpcsig.UnaryServerInterceptor(operatorPub,
//     grpcsig.WithChainCache(valiss.NewMemoryChainCache()))

NewMemoryChainCache is process-local; multiple receiver instances avoid per-instance warmups by backing valiss.ChainCache with shared storage. Only chains that survive full verification enter the cache, so it cannot be poisoned. Negotiation needs a backchannel: one-way payloads (queue messages, exported documents) keep the embedded chain. A receiver that pins one emitter's chain in configuration (WithVerifyOptions(valiss.WithChainTokens(...))) answers bare tokens on the first attempt with no cache at all.

Multiple trusted operators

A consumer receiving messages from several independent producers verifies against a keyring instead of a single anchor. Every entry is a full self-signed operator token — never a bare public key — so each trust domain always carries a name, an epoch, and a validity window, and per-domain policy is enforced on every verification:

k, _ := valiss.NewKeyring(prodUSOperatorToken, onPremOperatorToken)
claims, err := valiss.VerifyMessageKeyring(tok, k, valiss.WithPayload(body))
// claims.Operator.Name distinguishes trust domains: two producers can both
// have a tenant "acme"; segment by claims.Operator.Name + "/" + claims.Account.Name.

// Transports: httpsig.NewKeyringMiddleware(k), grpcsig.KeyringUnaryServerInterceptor(k).

Entries are selected by issuer, not by trial: the chain names its operator (the account token's issuer) and its epoch, and verification runs against exactly that entry — an unknown operator, or a known one at an unregistered epoch, fails immediately. The keyring rejects two operators sharing a name, so a name maps to exactly one key. One key may hold entries at several epochs: that is the receiver-side rotation grace period — register the new-epoch token next to the old, let the producer re-mint at its own pace, and bound the window by minting the transitional old-epoch token with a short expiry, closing the grace cryptographically.

The same keyring authenticates requests: NewKeyringVerifier(k, allowlist) is the multi-operator counterpart of NewVerifier, and drops into the existing transports unchanged (httpauth.NewMiddleware, grpcauth.NewAuthenticator take a *Verifier either way). Handlers read the trust domain from Identity.Operator. Entry policy is always enforced; WithOperatorToken does not combine with a keyring. The allowlist is shared across domains — account token jtis are content hashes and cannot collide between producers.

Extensions

All authorization rides named extension claims: signed, typed payloads under the token's ext field. An extension is any struct with an ExtensionName() string method; valiss signs and transports it opaquely, and the same concrete type comes back out on the server. Transport authorization is built on this mechanism:

  • contrib/httpauth defines Ext{Hosts, Methods, Paths} (name http): the middleware rejects requests outside the extension's bounds with 403.
  • contrib/grpcauth defines Ext{Methods} (name grpc): the interceptors reject methods outside the extension with PermissionDenied.

Transport enforcement is fail-closed: every token in the chain must carry the transport extension, the zero-value extension grants nothing, and allow-all is the explicit wildcard (Methods: ["*"], Paths: ["*"]). Deployments that authorize entirely outside the transport can opt out with AllowMissingExtension() on the authenticator/middleware. Extensions present on both chain levels are both enforced, so an account-level extension bounds every user of the account.

httpauth.Ext has three independent dimensions (hosts, methods, paths) and each constrains only when populated: a dimension you leave empty imposes no restriction on it. So Ext{Paths: ["/admin/*"]} permits /admin/* with any method — to scope a read-only admin surface, name every dimension: Ext{Methods: ["GET"], Paths: ["/admin/*"]}. (The single-dimension grpcauth.Ext has no other dimensions to leave open.)

Domain claims work the same way. Define the type, mint it into the token, recover it in the handler:

// The set of filters this application enforces on data queries.
type QueryFilters struct {
    Regions []string `json:"regions"`
}

func (QueryFilters) ExtensionName() string { return "acme.filters" }

// Mint: transport bounds plus domain claims, typed end to end.
tok, _ := valiss.IssueUser(account, alicePub,
    valiss.WithName("alice"),
    valiss.WithExtension(grpcauth.Ext{Methods: []string{"/example.v1.Widgets/*"}}),
    valiss.WithExtension(QueryFilters{Regions: []string{"eu"}}),
    valiss.WithTTL(time.Hour),
)

// Handler: the concrete type back, no string plumbing.
id, _ := valiss.IdentityFromContext(ctx)
filters, ok, err := valiss.ExtOf[QueryFilters](id.User.Ext)

Optionally validate extensions at auth time instead of in handlers: valiss.WithExtensionType[QueryFilters]() on the verifier rejects requests whose tokens carry a malformed acme.filters claim, and valiss.ExtValidator(func(req, id, acct, user QueryFilters) error { ... }) runs typed domain checks inside the verification pipeline.

The per-request signature is bound to the transport's request context (HTTP method/host/path, gRPC full method), so a captured signature cannot authorize a different operation. To also suppress replay of the same operation within the skew window, enable a replay cache on the server and a nonce on the client:

verifier := valiss.NewVerifier(operatorPub, allowlist,
    valiss.WithReplayCache(valiss.NewMemoryReplayCache()))
transport, _ := httpauth.NewTransport(c, nil, httpauth.WithNonce())
// gRPC: grpcauth.NewCredentials(c, grpcauth.WithNonce())

NewMemoryReplayCache is process-local; for exactly-once across multiple server instances back WithReplayCache with shared storage keyed by nonce.

Bearer credentials: a user token minted with valiss.WithBearer() authenticates without a per-request signature (token-only, replayable); pair with TLS and a short validity window. Accounts never get bearer tokens.

Layout

  • root (valiss.dev/valiss) — token issue/verify (account, user, and message level), request sign/verify, allowlist, the request Verifier, extension plumbing, and IdentityFromContext
  • creds — client creds file (tokens + seed in one marker-delimited file)
  • contrib/httpauth — net/http middleware, client transport, HTTP extension
  • contrib/grpcauth — gRPC interceptors, per-RPC credentials, gRPC extension
  • contrib/ginauth, contrib/echoauth — Gin and Echo middleware over the httpauth verification core (client side stays httpauth.NewTransport)
  • contrib/httpsig — message-token transport and middleware for net/http
  • contrib/grpcsig — message-token unary interceptors for gRPC
  • contrib/ginsig, contrib/echosig — message-token middleware for Gin and Echo over the httpsig receiving core (emitters stay httpsig.NewTransport)
  • examples/ — runnable end-to-end demos, including the manifest-driven examples/minter credential minting tool

Library

Server (gRPC):

verifier := valiss.NewVerifier(operatorPubKey, allowlist)
auth := grpcauth.NewAuthenticator(verifier)
srv := grpc.NewServer(
    grpc.UnaryInterceptor(auth.UnaryInterceptor()),
    grpc.StreamInterceptor(auth.StreamInterceptor()),
)
// in a handler:
id, _ := valiss.IdentityFromContext(ctx) // id.Account.Name segments data,
                                         // id.User names the end user (nil
                                         // for account-level requests)

Client (gRPC):

c, _ := creds.Load("alice.creds")
rpcCreds, _ := grpcauth.NewCredentials(c)
conn, _ := grpc.NewClient(addr, grpc.WithPerRPCCredentials(rpcCreds), ...)

Server (HTTP):

mw := httpauth.NewMiddleware(valiss.NewVerifier(operatorPubKey, allowlist))
srv := &http.Server{Handler: mw(mux)}

Any router that takes standard func(http.Handler) http.Handler middleware (chi, gorilla/mux, ...) uses httpauth.NewMiddleware as-is. Gin and Echo get native middleware:

engine.Use(ginauth.NewMiddleware(verifier))  // Gin;  id, _ := ginauth.IdentityFrom(c)
app.Use(echoauth.NewMiddleware(verifier))    // Echo; id, _ := echoauth.IdentityFrom(c)

Client (HTTP):

c, _ := creds.Load("alice.creds")
transport, _ := httpauth.NewTransport(c, nil)
client := &http.Client{Transport: transport}

Runnable versions: go run ./examples/grpcauth, go run ./examples/httpauth, go run ./examples/ginauth, go run ./examples/echoauth; go run ./examples/httpsig and go run ./examples/grpcsig demo per-message proofs of origin end to end, including post-expiry re-verification of a stored message and replay/tamper rejection.

Servers that hold account tokens in static configuration accept user-token-only requests via valiss.WithAccountTokenResolver(valiss.StaticAccountTokens(...)).

Minter

examples/minter is a manifest-driven credential minting tool (and a worked example of the issuance API). Stateless: key pairs print once and are never stored; signing seeds come from VALISS_SEED_<PUBKEY> environment variables.

go run ./examples/minter keygen operator     # public key = server trust anchor
go run ./examples/minter keygen account      # per-tenant key pair
go run ./examples/minter keygen user         # per-end-user key pair
go run ./examples/minter creds ACCOUNT[/USER]  # mint creds for a manifest entry

creds reads minter.yaml (annotated example in the directory), resolves seeds from the environment, and writes the creds to stdout with metadata — including the allowlist jti — on stderr. User creds carry only the user token by default; -bundle embeds a fresh account token for servers without a resolver. See examples/minter/README.md.

Interoperability

valiss tokens are structurally JWTs (JWS Compact Serialization, RFC 7519 claim names, standard Ed25519 signatures over the standard signing input) — but they are not verifiable with stock JWT libraries, deliberately:

  • The algorithm identifier is ed25519-nkey, not the registered EdDSA, so RFC-compliant libraries reject the header outright.
  • iss and sub carry nkey-encoded public keys (role prefix + base32 + CRC16), not JWKs.

This is a considered trade, not an accident. The key's role — operator, account, user — is part of the key material itself, so the strict signing hierarchy (operator keys sign account tokens, account keys sign user tokens, never the reverse) is checkable from the key string alone at every hop of the chain walk; a whole class of cross-level confusion bugs cannot be expressed. The encoding is also copy-paste-safe for the places keys actually live (config files, env vars, CLI flags): uppercase base32 with a checksum, so a corrupted key fails parsing instead of failing verification mysteriously. Standard-library compatibility would buy little in return: no stock JWT middleware can walk a four-level chain, echo epochs, or enforce payload checksums anyway — the signature check is the only layer it could reuse.

The library is the canonical verifier. Consumers in other languages port verification instead; it is a couple hundred lines with no valiss-specific cryptography — standard Ed25519 plus JSON — and docs/VERIFYING.md is the complete recipe (wire format, nkey decoding, chain-walk and message-verification algorithms, request signatures).

Prior art

valiss stands on well-trodden ground; these shaped it or solve neighboring problems:

  • NATS JWT authentication and nsc — the operator/account/user delegation model and the creds-file format originate here; valiss adapts them to request/response services and strips the messaging-specific parts.
  • RFC 7519 (JWT) — the claims vocabulary (iss, sub, exp, nbf, iat, jti) and token framing.
  • RFC 8032 (Ed25519) — the signature scheme, via nkeys.
  • golang-jwt — the embedded registered-claims struct pattern valiss's typed claims follow.
  • Biscuit and Macaroons — offline attenuation and delegation of bearer credentials; valiss trades their in-token attenuation for a fixed two-hop chain plus typed extension claims.
  • SPIFFE/SPIRE — workload identity with short-lived documents and trust-domain semantics; heavier machinery aimed at infrastructure identity rather than tenant credentials.

AI assistance

Substantial parts of this library — code, tests, and documentation — were written with AI assistance (Claude Code). Design decisions, the security model, and review are human; treat the maintainer as the author accountable for every line.

Documentation

Overview

Package valiss implements the core of the tenant authentication scheme, a three-level chain of Ed25519 keys:

  • An operator holds an Ed25519 nkey; its public key is the trust anchor servers pin.
  • The operator signs each tenant an account token that the tenant's own account nkey must back. Issued account tokens are recorded in a server-side allowlist.
  • A tenant may delegate: it signs user tokens with its account seed.
  • The subject signs every request with its nkey; the server verifies the token chain up to the pinned operator key, the request signature against the token's subject key, and the account token against the allowlist, then hands the verified identity to the handler for data segmentation.

Tokens are this scheme's own typed claims carried in an nkey-signed JWT: the sub claim is the subject's public key and name carries the human-readable label. Key levels are strict: operator keys (SO.../O...) sign account tokens for account keys (SA.../A...), account keys sign user tokens for user keys (SU.../U...).

A user key may additionally mint per-message tokens (IssueMessage): a fourth, optional chain level binding a token to a destination and payload checksum, offline-verifiable by anyone holding the operator public key (VerifyMessage). Message tokens are proofs of origin, never credentials: possession grants nothing, and the request Verifier does not accept them.

Authorization rides named extension claims (Extension, WithExtension): typed payloads the scheme signs and transports but assigns no meaning. The contrib transports enforce their extensions (HTTP hosts/methods/paths, gRPC methods); consumers add domain extensions the same way and read them back in handlers with ExtOf.

Index

Constants

View Source
const (
	HeaderAccountToken = "valiss-account-token"
	HeaderUserToken    = "valiss-user-token"
	HeaderTimestamp    = "valiss-timestamp"
	HeaderSignature    = "valiss-signature"
	HeaderNonce        = "valiss-nonce"
	// HeaderMessageToken carries a per-message proof of origin
	// (IssueMessage); it is never a request credential.
	HeaderMessageToken = "valiss-message-token"
	// HeaderChainAccountToken and HeaderChainUserToken carry a message
	// token's provenance chain detached from the token itself, so no single
	// header grows large. They are chain material for VerifyMessage, never
	// request credentials.
	HeaderChainAccountToken = "valiss-chain-account-token"
	HeaderChainUserToken    = "valiss-chain-user-token"
	// HeaderChain is the chain-negotiation signal a receiver sends back
	// (response header on HTTP, trailing metadata on gRPC) with the value
	// ChainRequired when it cannot verify a chainless message token; the
	// emitting transport then retries once with the detached chain headers.
	HeaderChain = "valiss-chain"
)

Header field names carrying the credential on each request. Used as gRPC metadata keys and HTTP header names alike.

View Source
const ChainRequired = "required"

ChainRequired is the HeaderChain value asking the emitter to retransmit with its provenance chain attached.

View Source
const DefaultMessageTTL = 30 * time.Second

DefaultMessageTTL is the validity window the contrib transports mint message tokens with: long enough for delivery latency and clock drift, short enough to bound capture exposure.

View Source
const DefaultSkew = 2 * time.Minute

DefaultSkew bounds request-timestamp drift and token-expiry slack.

Variables

View Source
var ErrNoChain = errors.New("valiss: message token carries no chain and none was supplied (WithChainTokens)")

ErrNoChain reports a message token that neither embeds a provenance chain nor had one supplied (WithChainTokens). Receiving transports match it with errors.Is to drive chain negotiation: it is the one verification failure a retransmit with the chain can cure.

Functions

func Checksum

func Checksum(payload []byte) string

Checksum returns the lowercase-hex SHA-256 of a payload exactly as delivered: the value WithChecksum embeds and WithPayload compares.

func ContextWithIdentity

func ContextWithIdentity(ctx context.Context, id *Identity) context.Context

ContextWithIdentity returns a context carrying the verified identity. Transport middlewares call this after verification.

func ContextWithMessage

func ContextWithMessage(ctx context.Context, c *MessageClaims) context.Context

ContextWithMessage returns a context carrying verified message claims. Receiving transports call this after VerifyMessage.

func Covered

func Covered(granted []string, required string) bool

Covered reports whether any granted pattern covers the required value, honoring trailing-"*" prefix wildcards. Transport extensions use it for paths and methods.

func ExtOf

func ExtOf[T Extension](exts Extensions) (T, bool, error)

ExtOf decodes the extension claim named by T's zero value. An absent name yields the zero value and false.

func Issue deprecated

func Issue(operator nkeys.KeyPair, tenantPubKey string, opts ...IssueOption) (string, error)

Issue mints an account token signed by the operator key.

Deprecated: Use IssueAccount, which names the minted level the way IssueOperator, IssueUser, and IssueMessage do.

func IssueAccount

func IssueAccount(operator nkeys.KeyPair, tenantPubKey string, opts ...IssueOption) (string, error)

IssueAccount mints an account token signed by the operator key. The token subject is the tenant's account public key and WithName carries the tenant id; the tenant signs requests with the seed matching the subject key.

func IssueMessage

func IssueMessage(user nkeys.KeyPair, opts ...IssueOption) (string, error)

IssueMessage mints a per-message proof-of-origin token signed by the emitter's user key over itself (iss == sub). WithAudience binds it to a destination, WithChecksum to the payload bytes, and WithChain embeds the provenance chain so a receiver verifies offline with only the operator public key (VerifyMessage). Message tokens must carry an expiry: they are short-lived proofs, and an eternal proof of origin only widens capture exposure.

func IssueOperator

func IssueOperator(operator nkeys.KeyPair, opts ...IssueOption) (string, error)

IssueOperator mints the self-signed operator token: the trust domain's policy statement (epoch, validity window, extensions), signed by the operator key over its own public key. WithName labels the trust domain the way account and user names label theirs. Servers configured with the token via WithOperatorToken enforce the policy on every request; the pinned public key remains the trust anchor.

func IssueUser

func IssueUser(account nkeys.KeyPair, userPubKey string, opts ...IssueOption) (string, error)

IssueUser mints a user token signed by a tenant's account key, delegating to an end user. The token subject is the user's public key and WithName carries the user id. WithBearer produces a token the server accepts without per-request signatures.

func IssuerOf

func IssuerOf(token string) (string, error)

IssuerOf returns the public key that signed a token, after checking the token's own signature against it. It does not establish trust: the caller must still verify the issuer's place in the chain.

func NewNonce

func NewNonce() string

NewNonce returns a fresh random per-request nonce (128 bits, hex). Client transports use it when a replay cache is in play; see WithReplayCache.

func SignRequest

func SignRequest(subject nkeys.KeyPair, now time.Time, reqContext []byte) (timestamp, signature string, err error)

SignRequest produces the timestamp and base64 signature a subject attaches to a request, signing the timestamp bound to reqContext with its nkey seed. reqContext is the transport's canonical description of the request (e.g. method and path); the server must reconstruct identical bytes. Pass nil to bind nothing beyond the timestamp.

func VerifySignature

func VerifySignature(subjectPubKey, timestamp, signature string, reqContext []byte, now time.Time, skew time.Duration) error

VerifySignature checks a request signature against the subject public key, bounds the timestamp to a symmetric skew window around now, and confirms it was signed over reqContext. reqContext must match the bytes the client signed (see SignRequest).

Types

type AccountClaims

type AccountClaims struct {
	Claims
	// Name is the tenant's human-readable label; it segments all stored
	// data. Falls back to the subject key when the token carries no name.
	Name string
	// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
	Epoch uint64
	// Ext carries the named extension claims (WithExtension).
	Ext Extensions
}

AccountClaims is the verified content of an account (tenant) token.

func VerifyAccount

func VerifyAccount(token, operatorPubKey string) (*AccountClaims, error)

VerifyAccount decodes an account token, checks its type, signature, and issuer, and returns the claims. It does NOT check expiry, activation, or the allowlist; the Verifier layers those so callers get precise errors.

type AccountTokenResolver

type AccountTokenResolver func(accountPubKey string) (string, error)

AccountTokenResolver supplies the operator-signed account token for an account public key, serving requests that carry only a user token (the default creds shape). The resolved token goes through the full verification: operator signature, expiry, allowlist.

func StaticAccountTokens

func StaticAccountTokens(tokens ...string) (AccountTokenResolver, error)

StaticAccountTokens builds a resolver over a fixed token set, e.g. from server configuration. Tokens are indexed by their subject account key; their signatures are checked here, their trust is established per request.

type AllowAll

type AllowAll struct{}

AllowAll accepts every token; for local development where no allowlist is configured. The token signature and expiry still gate access.

func (AllowAll) Allowed

func (AllowAll) Allowed(string) bool

type Allowlist

type Allowlist interface {
	Allowed(jti string) bool
}

Allowlist decides whether an issued token (by jti) is still accepted. Only tokens the issuer explicitly deposited server-side pass, so a token can be revoked by removing it even before expiry.

type ChainCache

type ChainCache interface {
	// Get returns the cached chain tokens for an emitter's user public key.
	Get(userPubKey string) (accountToken, userToken string, ok bool)
	// Put stores the chain tokens for an emitter's user public key.
	Put(userPubKey, accountToken, userToken string)
	// Del drops the entry, e.g. when verification against it fails after a
	// domain rotation.
	Del(userPubKey string)
}

ChainCache stores verified provenance chains keyed by the emitter's user public key, serving the receiving side of chain negotiation: an emitter sends chainless message tokens, the receiver caches the chain from the one retransmit that carried it, and every later message verifies against the cached copy. Implementations must be safe for concurrent use and may be backed by memory or shared storage. Store only chains that survived a full VerifyMessage, so the cache never holds material an attacker could plant.

type Claims

type Claims struct {
	// ID is the token's unique identifier (jti), the allowlist key for
	// account tokens.
	ID string
	// Issuer is the issuer public key that signed the token (iss).
	Issuer string
	// Subject is the subject's nkey public key (sub) that must sign
	// requests.
	Subject string
	// IssuedAt is the token mint time (iat).
	IssuedAt time.Time
	// ExpiresAt is the token expiry (exp); zero means the token never
	// expires.
	ExpiresAt time.Time
	// NotBefore is the token activation time (nbf); zero means immediately
	// valid.
	NotBefore time.Time
}

Claims is the verified RFC 7519 registered-claims content of a token. Level-specific data lives on AccountClaims and UserClaims, which embed it.

func Decode

func Decode(token string) (*Claims, error)

Decode parses a token of either level without establishing trust: the signature is checked against the token's own embedded issuer only. For inspection and tooling; servers must use VerifyAccount or VerifyUser.

func (*Claims) Expired

func (c *Claims) Expired(now time.Time, skew time.Duration) bool

Expired reports whether the token has passed its expiry (with skew slack).

func (*Claims) NotYetValid

func (c *Claims) NotYetValid(now time.Time, skew time.Duration) bool

NotYetValid reports whether the token's not-before still lies in the future (with skew slack).

type ClaimsValidator

type ClaimsValidator func(req Request, id *Identity) error

ClaimsValidator is custom validation logic injected into the Verifier. It runs after the token chain is verified, the identity is assembled, and the request signature has proven possession of the subject seed (a bearer user token waives the signature). A non-nil error rejects the request as unauthenticated. Running post-possession means an expensive validator is never triggered by a party that merely captured a token but cannot sign.

func ExtValidator

func ExtValidator[T Extension](fn func(req Request, id *Identity, acct, user T) error) ClaimsValidator

ExtValidator adapts a typed validator over the extension claim named by T's zero value into a ClaimsValidator: the extension is decoded from both the account and user tokens before fn runs. Missing extensions pass zero values.

type Extension

type Extension interface {
	ExtensionName() string
}

Extension is a named claim payload carried in a token's ext field. Implementations are value struct types whose zero value reports the name.

type Extensions

type Extensions map[string]json.RawMessage

Extensions is the named extension claims of a token: consumer- or transport-defined claim bodies keyed by extension name. This scheme signs and transports them; meaning is assigned by whoever registered the name (e.g. the httpauth and grpcauth packages, or the library consumer).

type Identity

type Identity struct {
	// Account is the tenant the request acts under; always present.
	Account *AccountClaims
	// User is the delegated end user; nil for account-level requests.
	User *UserClaims
	// Operator is the trust domain the request verified under: the keyring
	// entry on a NewKeyringVerifier, the policy token under
	// WithOperatorToken, nil otherwise. Consumers trusting several
	// operators segment by Operator.Name — the keyring guarantees a name
	// maps to exactly one operator key.
	Operator *OperatorClaims
}

Identity is the verified result of a request.

func IdentityFromContext

func IdentityFromContext(ctx context.Context) (*Identity, bool)

IdentityFromContext returns the verified identity a handler uses to segment data. The bool is false on unauthenticated contexts.

type IssueOption

type IssueOption func(*issueConfig)

IssueOption customizes a minted token. Without a WithTTL or WithExpiry option the token never expires.

func WithAudience

func WithAudience(aud string) IssueOption

WithAudience binds a message token to its destination (the JWT aud claim): a URL, queue name, or recipient id. Receivers that verify with ExpectAudience reject tokens minted for anywhere else, closing cross-destination replay. Only IssueMessage accepts this option.

func WithBearer

func WithBearer() IssueOption

WithBearer marks a user token as a bearer token: the server accepts it without per-request signatures. Bearer tokens are replayable until they expire or their account leaves the allowlist, so pair them with TLS and a short validity window. Only IssueUser accepts this option.

func WithChain

func WithChain(accountToken, userToken string) IssueOption

WithChain embeds the emitter's provenance chain — the operator-signed account token and the account-signed user token — into the message token, so a receiver needs nothing but the operator public key (see also the verify-side WithChainTokens for out-of-band delivery). Only IssueMessage accepts this option.

func WithChecksum

func WithChecksum(sum string) IssueOption

WithChecksum embeds the lowercase-hex SHA-256 of the message payload exactly as delivered (see Checksum), binding the token to the bytes. Receivers compare it via WithPayload or read it from MessageClaims. Only IssueMessage accepts this option.

func WithEpoch

func WithEpoch(epoch uint64) IssueOption

WithEpoch stamps the trust-domain epoch on the token. On an operator token it declares the domain's current epoch; on account and user tokens it must echo it. Verifiers configured with the operator token reject tokens from any other epoch, so bumping the epoch and re-minting rotates the whole domain at once. Unstamped tokens are epoch 0.

func WithExpiry

func WithExpiry(t time.Time) IssueOption

WithExpiry makes the token expire at t (the JWT exp claim).

func WithExtension

func WithExtension(v Extension) IssueOption

WithExtension embeds a named extension claim into the token's ext field; the name comes from the value's ExtensionName. Repeat the option for multiple extensions; a duplicate name is an error. The scheme signs and transports the value untouched; servers read it back with ExtOf or validate it with an ExtValidator.

func WithName

func WithName(name string) IssueOption

WithName labels the minted entity with a human-readable name: the trust domain on an operator token, the tenant on an account token, the user on a user token. Names are optional; an unnamed entity is represented by its public key. Names are asserted by their issuer, and nothing at issuance checks uniqueness: collections that hold several entities side by side (an anchor keyring, a tenant directory) own the uniqueness of names in their scope. Message tokens carry no name.

func WithNotBefore

func WithNotBefore(t time.Time) IssueOption

WithNotBefore makes the token invalid before t (the JWT nbf claim).

func WithTTL

func WithTTL(ttl time.Duration) IssueOption

WithTTL makes the token expire ttl from now (the JWT exp claim).

type Keyring

type Keyring struct {
	// contains filtered or unexported fields
}

Keyring is the set of trusted operators a multi-producer consumer verifies against. Every entry is a full self-signed operator token — never a bare public key — so each trust domain always carries a name, an epoch, and a validity window, and per-domain policy is enforced on every verification.

Entries are selected by issuer, not by trial: an incoming chain names its operator (the account token's issuer) and its epoch, and verification runs against exactly the entry registered under that (key, epoch) pair. An unknown pair fails immediately.

One operator key may hold entries at several epochs, which is how a receiver grants a rotation grace period: register the new-epoch token next to the old one, let producers re-mint at their own pace, and drop (or simply let expire) the old entry. Whether and how long two epochs coexist is always the receiver's choice.

A Keyring is immutable after construction and safe for concurrent use.

func NewKeyring

func NewKeyring(operatorTokens ...string) (*Keyring, error)

NewKeyring builds a keyring from self-signed operator tokens. Identical tokens (same jti) collapse into one entry. Registration fails on: a different token for an already-occupied (operator key, epoch) pair, two operator keys sharing a name, or one operator key naming itself differently across entries. Unnamed operators are represented by their public key.

type MemoryChainCache

type MemoryChainCache struct {
	// contains filtered or unexported fields
}

MemoryChainCache is a process-local ChainCache. For fewer warmup round-trips across multiple receiver instances, back ChainCache with shared storage instead.

func NewMemoryChainCache

func NewMemoryChainCache() *MemoryChainCache

func (*MemoryChainCache) Del

func (c *MemoryChainCache) Del(userPubKey string)

func (*MemoryChainCache) Get

func (c *MemoryChainCache) Get(userPubKey string) (string, string, bool)

func (*MemoryChainCache) Put

func (c *MemoryChainCache) Put(userPubKey, accountToken, userToken string)

type MemoryReplayCache

type MemoryReplayCache struct {
	// contains filtered or unexported fields
}

MemoryReplayCache is an in-memory ReplayCache that retains each nonce until its expiry and prunes lazily. It is process-local: for exactly-once across multiple server instances, back WithReplayCache with shared storage instead. Safe for concurrent use.

func NewMemoryReplayCache

func NewMemoryReplayCache() *MemoryReplayCache

NewMemoryReplayCache returns an empty in-memory replay cache.

func (*MemoryReplayCache) SeenBefore

func (c *MemoryReplayCache) SeenBefore(nonce string, expiry time.Time) bool

SeenBefore records nonce with the given expiry and reports whether a still-valid entry was already present.

type MessageClaims

type MessageClaims struct {
	Claims
	// Audience is the destination the token was minted for (aud); empty
	// when the token is unbound.
	Audience string
	// Checksum is the lowercase-hex SHA-256 of the payload the token was
	// minted over; empty when the token carries no payload binding.
	Checksum string
	// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
	Epoch uint64
	// Ext carries the named extension claims (WithExtension).
	Ext Extensions
	// Account is the verified tenant identity from the chain. Offline
	// receivers hold no allowlist; an online receiver that wants revocation
	// checks Account.ID against its own Allowlist.
	Account *AccountClaims
	// User is the verified emitter identity from the chain; its subject key
	// signed the message token.
	User *UserClaims
	// Operator is the trust domain the message verified under: the keyring
	// entry on VerifyMessageKeyring, the policy token on VerifyMessage with
	// WithOperatorPolicy, nil otherwise. Consumers trusting several
	// operators segment by Operator.Name — the keyring guarantees a name
	// maps to exactly one operator key.
	Operator *OperatorClaims
}

MessageClaims is the verified content of a message token: a per-message proof of origin, together with the verified chain identities it was checked against. A message token is a proof, not a credential: possession grants nothing, and Verifier.VerifyRequest never accepts one.

func MessageFromContext

func MessageFromContext(ctx context.Context) (*MessageClaims, bool)

MessageFromContext returns the verified message claims a handler uses to attribute an incoming message to its emitter. The bool is false when no message token was verified. Message claims prove origin only; they are not an identity and grant nothing.

func VerifyMessage

func VerifyMessage(token, operatorPubKey string, opts ...VerifyMessageOption) (*MessageClaims, error)

VerifyMessage verifies a per-message proof of origin against the pinned operator public key: it walks the chain operator → account → user → message, requires all chain levels to agree on the epoch, checks every validity window at the verification instant (At; default now), and enforces the audience and checksum bindings the options request. On success the returned claims carry the verified tenant and emitter identities alongside the message bindings.

A verified message token proves origin only. It is not a credential: grant nothing for possession of one.

func VerifyMessageKeyring

func VerifyMessageKeyring(token string, keyring *Keyring, opts ...VerifyMessageOption) (*MessageClaims, error)

VerifyMessageKeyring verifies a per-message proof of origin against a set of trusted operators (see Keyring). The chain names its trust domain — the account token's issuer and epoch select exactly one keyring entry — and verification then runs as VerifyMessage does under that entry's always-enforced policy: the entry token's validity window at the verification instant and its exact epoch. A chain from an unknown operator, or a known operator at an unregistered epoch, fails immediately. The returned claims carry the matched entry as Operator.

WithOperatorPolicy does not combine with a keyring: entries carry the policy.

type OperatorClaims

type OperatorClaims struct {
	Claims
	// Name is the trust domain's human-readable label. It is asserted by
	// the operator about itself: a consumer trusting several operators must
	// not assume names are unique across domains. Falls back to the
	// operator public key when the token carries no name.
	Name string
	// Epoch is the trust domain's current epoch. A verifier configured with
	// the operator token accepts only account and user tokens that echo it.
	Epoch uint64
	// Ext carries the named extension claims (WithExtension).
	Ext Extensions
}

OperatorClaims is the verified content of a self-signed operator token: the trust domain's policy statement, signed by the pinned anchor key.

func VerifyOperator

func VerifyOperator(token, operatorPubKey string) (*OperatorClaims, error)

VerifyOperator decodes a self-signed operator token, checks its type and that it is signed by the pinned operator key over itself, and returns the claims. Expiry and activation checks belong to the Verifier.

type ReplayCache

type ReplayCache interface {
	// SeenBefore records nonce with the given expiry and reports whether it
	// was already present. expiry is when the entry may be discarded.
	SeenBefore(nonce string, expiry time.Time) bool
}

ReplayCache records request nonces and reports duplicates within a retention window, so a captured request cannot be replayed for the same operation before its signature ages out. Implementations must be safe for concurrent use and may be backed by memory or shared storage.

type Request

type Request struct {
	// AccountToken is the operator-signed account token.
	AccountToken string
	// UserToken is the account-signed user token on chain credentials; empty
	// when the tenant itself makes the request.
	UserToken string
	// Timestamp and Signature are the per-request signing proof; both empty
	// on bearer requests.
	Timestamp string
	Signature string
	// Context is the transport's canonical description of the request (e.g.
	// method and path) that the signature is bound to. The transport fills
	// it from the incoming request and the client signs the identical bytes;
	// a mismatch fails the signature. Nil binds nothing beyond the timestamp.
	Context []byte
	// Nonce is a per-request unique value, folded into Context by the
	// transport so it is signed. A Verifier with a ReplayCache requires it
	// and rejects a nonce it has already seen.
	Nonce string
}

Request is the per-request material a transport extracts from headers.

type StaticAllowlist

type StaticAllowlist struct {
	// contains filtered or unexported fields
}

StaticAllowlist is an in-memory set of accepted token IDs.

func LoadAllowlistFile

func LoadAllowlistFile(path string) (*StaticAllowlist, error)

LoadAllowlistFile reads a newline-delimited allowlist file of token IDs. Blank lines and lines beginning with '#' are ignored.

func NewStaticAllowlist

func NewStaticAllowlist(ids ...string) *StaticAllowlist

func (*StaticAllowlist) Allowed

func (a *StaticAllowlist) Allowed(jti string) bool

func (*StaticAllowlist) Set

func (a *StaticAllowlist) Set(ids []string)

Set replaces the accepted set, e.g. after reloading the file.

type UserClaims

type UserClaims struct {
	Claims
	// Name is the user's human-readable label. Falls back to the subject
	// key when the token carries no name.
	Name string
	// Epoch is the trust-domain epoch the token was issued in (WithEpoch).
	Epoch uint64
	// Bearer marks a token whose holder authenticates by the token alone,
	// without per-request signatures.
	Bearer bool
	// Ext carries the named extension claims (WithExtension).
	Ext Extensions
}

UserClaims is the verified content of a user token.

func VerifyUser

func VerifyUser(token, accountPubKey string) (*UserClaims, error)

VerifyUser decodes a user token, checks its type, signature, and issuer (the account public key that delegated it), and returns the claims. Expiry and activation checks belong to the Verifier.

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier checks the full per-request credential: account token signature against the pinned operator key, expiry and activation, allowlist membership, the optional user-token chain, registered extension types, custom validators, and the request signature within the skew window. Requests without a signature pass only when the effective token is a bearer user token. Transport layers (gRPC interceptor, HTTP middleware) wrap it with header extraction and error mapping.

func NewKeyringVerifier

func NewKeyringVerifier(keyring *Keyring, allowlist Allowlist, opts ...VerifierOption) *Verifier

NewKeyringVerifier is NewVerifier for a server trusting several operators (see Keyring). The credential names its trust domain — the account token's issuer and epoch select exactly one keyring entry — and the request then verifies under that entry's always-enforced policy: the entry token's validity window and its exact epoch, echoed by every chain level. A credential from an unknown operator, or a known operator at an unregistered epoch, is rejected. Handlers tell trust domains apart by Identity.Operator.

WithOperatorToken does not combine with a keyring: entries carry the policy. The allowlist is shared across domains; account token jtis are content hashes, so entries cannot collide between producers.

func NewVerifier

func NewVerifier(operatorPubKey string, allowlist Allowlist, opts ...VerifierOption) *Verifier

func (*Verifier) VerifyRequest

func (v *Verifier) VerifyRequest(req Request) (*Identity, error)

VerifyRequest authenticates a request credential and returns the verified identity. Any error means the request must be rejected as unauthenticated.

A credential with a user token is verified as a chain: the account token against the operator key and the allowlist, then the user token against the account token's subject key. An empty timestamp and signature is a bearer request, accepted only when the effective token is a bearer user token; account-level requests must always sign.

type VerifierOption

type VerifierOption func(*Verifier)

VerifierOption configures a Verifier.

func WithAccountTokenResolver

func WithAccountTokenResolver(fn AccountTokenResolver) VerifierOption

WithAccountTokenResolver accepts requests that carry only a user token, resolving the account token server-side. Without it such requests are rejected.

func WithClaimsValidator

func WithClaimsValidator(fn ClaimsValidator) VerifierOption

WithClaimsValidator injects custom validation into the verification pipeline. Validators run in registration order; the first error wins.

func WithClock

func WithClock(now func() time.Time) VerifierOption

WithClock overrides the time source; for tests.

func WithExtensionType

func WithExtensionType[T Extension]() VerifierOption

WithExtensionType registers an extension type for eager validation: when either token carries the extension named by T's zero value, it must decode into T or the request is rejected. Retrieval via ExtOf never requires registration; this only moves malformed-extension failures to auth time.

func WithOperatorToken

func WithOperatorToken(token string) VerifierOption

WithOperatorToken supplies the trust domain's self-signed operator token and enforces its policy on every request: the operator token must be within its own validity window, and every account and user token must echo its epoch. Bumping the epoch in a fresh operator token therefore revokes everything minted in earlier epochs at once. The pinned operator public key remains the trust anchor; a token not self-signed by it poisons the verifier so every request fails rather than silently skipping policy.

func WithReplayCache

func WithReplayCache(cache ReplayCache) VerifierOption

WithReplayCache rejects a signed request whose nonce the cache has already seen, suppressing replay of the same request within the skew window. Signed requests must then carry a nonce (the client transport must enable it); bearer requests, which carry no signature, are unaffected. The nonce is retained for twice the skew window, the longest a replay could still land inside a valid timestamp window.

func WithSkew

func WithSkew(d time.Duration) VerifierOption

WithSkew overrides the DefaultSkew window for timestamp drift and token expiry slack.

type VerifyMessageOption

type VerifyMessageOption func(*verifyMessageConfig)

VerifyMessageOption customizes VerifyMessage.

func At

At evaluates the validity windows and operator policy as of the supplied instant instead of now, so stored messages verify after their tokens expire. Callers keeping key and epoch history pass the instant the message was received.

func ExpectAudience

func ExpectAudience(aud string) VerifyMessageOption

ExpectAudience requires the message token to be bound to exactly this destination; a token minted for anywhere else — or bound to no audience at all — is rejected. This is the receiver's lever against cross-destination replay; every receiver that knows its own identity should set it.

func RequireChecksum

func RequireChecksum() VerifyMessageOption

RequireChecksum rejects message tokens that carry no checksum claim, for receivers that insist every message is payload-bound. Comparison against the actual bytes still requires WithPayload.

func WithChainTokens

func WithChainTokens(accountToken, userToken string) VerifyMessageOption

WithChainTokens supplies the provenance chain out-of-band for message tokens minted without WithChain, trading self-containment for smaller tokens. A token that embeds a chain must embed this exact chain; a mismatch is an error.

func WithMessageSkew

func WithMessageSkew(d time.Duration) VerifyMessageOption

WithMessageSkew overrides the DefaultSkew slack applied to the validity windows of the message token and its chain.

func WithOperatorPolicy

func WithOperatorPolicy(operatorToken string) VerifyMessageOption

WithOperatorPolicy supplies the trust domain's self-signed operator token (the same token WithOperatorToken takes on a Verifier) and enforces its policy: the operator token must be within its own validity window at the verification instant, and the message token must echo the domain epoch.

func WithPayload

func WithPayload(payload []byte) VerifyMessageOption

WithPayload hashes the payload exactly as received and requires the token's checksum claim to match; a token without a checksum claim is rejected. This is the receiver's lever against payload tampering.

Directories

Path Synopsis
conformance
gen command
Command gen emits the valiss spec-1 conformance vectors.
Command gen emits the valiss spec-1 conformance vectors.
contrib
echoauth
Package echoauth wires the tenant authentication scheme into Echo: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
Package echoauth wires the tenant authentication scheme into Echo: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
echosig
Package echosig wires per-message proofs of origin (valiss message tokens) into Echo: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
Package echosig wires per-message proofs of origin (valiss message tokens) into Echo: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
ginauth
Package ginauth wires the tenant authentication scheme into Gin: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
Package ginauth wires the tenant authentication scheme into Gin: a middleware that verifies the per-request credential and enforces the HTTP extension claim (httpauth.Ext), built on httpauth's verification core.
ginsig
Package ginsig wires per-message proofs of origin (valiss message tokens) into Gin: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
Package ginsig wires per-message proofs of origin (valiss message tokens) into Gin: a middleware that verifies the token on every incoming request, built on httpsig's receiving core, including the receiving side of chain negotiation.
grpcauth
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential.
Package grpcauth wires the tenant authentication scheme into gRPC: server interceptors that verify the per-request credential and enforce the gRPC extension claim (Ext), and a client per-RPC credential that attaches the credential.
grpcsig
Package grpcsig wires per-message proofs of origin (valiss message tokens) into gRPC: a client unary interceptor that mints a token per call and a server unary interceptor that verifies it offline against the operator public key.
Package grpcsig wires per-message proofs of origin (valiss message tokens) into gRPC: a client unary interceptor that mints a token per call and a server unary interceptor that verifies it offline against the operator public key.
httpauth
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential.
Package httpauth wires the tenant authentication scheme into net/http: a server middleware that verifies the per-request credential and enforces the HTTP extension claim (Ext), and a client http.RoundTripper that attaches the credential.
httpsig
Package httpsig wires per-message proofs of origin (valiss message tokens) into net/http: a client Transport that mints a token per outgoing request and a server middleware that verifies it offline against the operator public key.
Package httpsig wires per-message proofs of origin (valiss message tokens) into net/http: a client Transport that mints a token per outgoing request and a server middleware that verifies it offline against the operator public key.
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file.
Package creds implements the client credentials file: the subject's token plus the seed that signs its requests, in one marker-delimited text file.
examples
echoauth command
Example echoauth shows the full tenant-auth wiring for Echo: an operator signs an account token carrying an HTTP extension, the server installs the echoauth middleware on the app, and the client signs every request via the framework-agnostic httpauth transport.
Example echoauth shows the full tenant-auth wiring for Echo: an operator signs an account token carrying an HTTP extension, the server installs the echoauth middleware on the app, and the client signs every request via the framework-agnostic httpauth transport.
ginauth command
Example ginauth shows the full tenant-auth wiring for Gin: an operator signs an account token carrying an HTTP extension, the server installs the ginauth middleware on the engine, and the client signs every request via the framework-agnostic httpauth transport.
Example ginauth shows the full tenant-auth wiring for Gin: an operator signs an account token carrying an HTTP extension, the server installs the ginauth middleware on the engine, and the client signs every request via the framework-agnostic httpauth transport.
grpcauth command
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call.
Example grpcauth shows the full tenant-auth wiring for gRPC: an operator issues an account token, the server installs the auth interceptors, and the client attaches the credential to every call.
grpcsig command
Example grpcsig shows per-message proofs of origin over gRPC: the client interceptor mints a token per unary call binding the full method and the request message, the server interceptor verifies it offline against the operator public key, and a captured token fails when replayed at another method or with a tampered message.
Example grpcsig shows per-message proofs of origin over gRPC: the client interceptor mints a token per unary call binding the full method and the request message, the server interceptor verifies it offline against the operator public key, and a captured token fails when replayed at another method or with a tampered message.
httpauth command
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport.
Example httpauth shows the full tenant-auth wiring for net/http: an operator signs an account token carrying an HTTP extension, the server wraps its mux with the auth middleware, and the client signs every request via the transport.
httpsig command
Example httpsig shows per-message proofs of origin over HTTP: an emitter posts a webhook through the httpsig transport, the receiver's middleware verifies the token offline against the operator public key, and an auditor re-verifies the stored message after the token has expired using verification-at-instant.
Example httpsig shows per-message proofs of origin over HTTP: an emitter posts a webhook through the httpsig transport, the receiver's middleware verifies the token offline against the operator public key, and an auditor re-verifies the stored message after the token has expired using verification-at-instant.
minter command
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model.
Command minter issues valiss tenant credentials for gRPC and HTTP services using the operator/account/user nkey model.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL