Skip to content
Registry StackDocsDevelopment (unreleased)

Configure Registry Mint

View as Markdown

Configure Registry Mint when a deployment needs to hand short-lived access tokens to a closed set of registered machine clients and has no identity provider to issue them.

Registry Mint is a supporting service, not a product pattern of its own. Use it when a resource server such as Evidence Gateway needs signed, expiring, audience-bound tokens and standing up a general-purpose identity provider is not an option for the deployment.

Version 1 runs one active Mint process. Its client-assertion replay cache is memory-only and clears on restart. Do not claim high availability or durable replay protection for this deployment shape.

Registry Mint answers a narrower question than a shared JWKS can. A pooled key set can only say that a token was signed by a trusted key; it cannot say which caller signed it or what that caller is permitted to assert. Registry Mint splits the two questions across two places: the client registry binds a client id to that client’s own public keys and to the authority Registry Mint will assert for it, and the token endpoint verifies an incoming request against the keys of the client it claims to be, then writes the authority from the registry, never from the request.

For the whole round trip in one picture, from a client holding only its own private key to a signed assertion, see how a client, Registry Mint, and Evidence Gateway interact.

Skip Registry Mint when an identity provider already issues client-credentials tokens for the deployment. Registry Mint exists only for the case where none does; pointing Evidence Gateway at an existing IdP’s token endpoint and JWKS does not require Registry Mint at all.

Registry Mint is also not a place to route caller identity for people. It authenticates registered machine clients by private key, and any token bound to one named person rides inside a client’s own signed request rather than a separate login. If a deployment needs a person to authenticate directly, that is an identity provider’s job, not Registry Mint’s.

You need:

  • The registered clients this deployment will serve: one client id, principal, evidence audience, and set of requester tags per client.
  • A governed public P-256 JWK for Registry Mint itself. Its service kid is the derived RFC 7638 thumbprint. Strict deployments use a workload-local Vault or OpenBao Transit proxy; supervised local development may use an owner-only local P-256 private JWK.
  • An independently generated audit HMAC key containing at least 32 bytes, stored in another owner-only, non-symlink file.
  • A private JWK per client. Registry Mint only ever stores and reads each client’s public half; keep the private half with the client.
  • A directory to hold one registration file per client.
  • An owner-only directory on durable storage for the keyed Mint audit chain. Plan its capacity, backup, and retention because Registry Mint rotates segments but never deletes or compacts them.
  • The claim names the resource server (Evidence Gateway, for example) reads its principal, requester tags, evidence audience, and grant pair from, so Registry Mint’s accessTokens.claims can be set to match them exactly.
  • TLS in front of Registry Mint for strict deployments. Registry Mint serves plain HTTP and expects TLS termination it does not manage. Supervised local development alone admits the exact http://127.0.0.1:<nonzero-port> issuer and matching token and JWKS paths.

Registry Mint reads one YAML document. Every relative path in it resolves against the document’s own directory, and every field in it is startup-only: changing issuer identity, signing keys, the listener, or token policy means restarting the process.

version: 1
validationMode: strict
issuer: https://mint.example.org
listener:
address: 127.0.0.1
port: 8081
signing:
algorithm: ES256
activePublicJwkFile: public-keys/<thumbprint>.jwk.json
publishedPublicJwkFiles: []
revokedKeyIds: []
signer:
kind: transit
unixSocketPath: /run/registry-mint/transit-proxy.sock
mount: transit
keyName: mint-signing
keyVersion: 7
timeoutMilliseconds: 2000
secretProviders:
file:
root: /run/registry-mint/secrets
audit:
path: audit/mint.jsonl
maximumFileBytes: 1073741824
hashKeyRef: secret:file/audit-hmac-key
hashKeyVersion: 1
accessTokens:
audiences: [evidence]
lifetimeSeconds: 300
claims:
principal: sub
requesterTags: evidence_tags
evidenceAudience: evidence_audience
grantId: evidence_grant_id
grantAuthority: evidence_authority
clientAssertion:
audience: https://mint.example.org/token
algorithms: [EdDSA, ES256, RS256]
clients:
directory: clients

issuer must be an https URL with a host and no credentials, query, or fragment; resource servers compare it exactly. accessTokens.lifetimeSeconds is bounded to 60..=3600: short enough that a leaked token expires quickly, long enough to survive verifier clock skew. accessTokens.claims must match the resource server’s own claim names field for field, because that is the only place the two configurations have to agree. clientAssertion.audience is the value every client’s signed request must carry as its own aud, which stops a request built for one endpoint from being replayed at another.

validationMode defaults to strict, which requires the Transit signer. Set it to supervised-local-development only for the disposable local developer environment; that mode may use signer.kind: local-jwk with privateKeyRef: secret:file/<name>. Use Configure Transit signing for Evidence Gateway and Registry Mint to provision a strict signer and its governed public JWK.

The secret named by audit.hashKeyRef must contain at least 32 bytes and remain separate from the signing key. Mint verifies the keyed JSONL chain and takes a single-writer lock at startup. Before returning an access token it synchronizes a token-release record to the chain and its parent directory. If the write fails, Mint returns server_error, does not release the token, and fails readiness. The chain keeps only keyed pseudonyms where correlation is needed, never raw assertions, access tokens, client ids, authority values, actors, or subject values. audit.maximumFileBytes is the per-segment rotation threshold. Mint seals a full segment as <audit.path>.<eight-digit-sequence> and continues online at audit.path; it never deletes or compacts sealed segments.

Delegated tokens, bound to one named subject a client acts on behalf of, are a further optional step layered on top of this base configuration. crates/registry-mint/README.md covers the accessTokens.claims.actor field and the per-client delegation block that step needs; this walkthrough covers the base, undelegated flow.

For the complete field list, including every default, see the Registry Mint reference.

Add one file per client under the directory named in clients.directory:

clientId: health-desk
principal: service:health-desk
evidenceAudience: https://evidence.example.org
requesterTags: [health-desk, region-north]
keys:
- kty: OKP
crv: Ed25519
kid: health-desk-2026-01
x: "<public-key-x-coordinate>"

keys accepts public JWKs only; a document carrying a private key member is rejected outright. Loading the client registry is all-or-nothing, so one malformed registration fails the whole load rather than serving a partial registry.

Validate the deployment before opening a socket:

Terminal window
mint check --config /etc/mint/mint.yaml

check loads the configuration, governed public keys, signer, audit key, and client registry, then performs the signer self-test before it exits. It deliberately does not open the audit chain, which admits one writer at a time, so you can check an edited configuration against the deployment it is about to replace. check, serve, and verify-audit accept MINT_CONFIG in place of --config.

Terminal window
mint serve --config /etc/mint/mint.yaml

Onboarding, offboarding, and caller key rotation only need the client registry reloaded, not the process restarted: send the running process SIGHUP and it re-reads clients.directory, keeping the previous registry in place if the new one fails to load.

A client authenticates with the client_credentials grant and private_key_jwt client authentication (RFC 7523): it signs a short-lived JWT assertion with its own private key and posts it to the token endpoint.

Terminal window
curl -sS https://mint.example.org/token \
-d grant_type=client_credentials \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode "client_assertion=<compact-jws-client-assertion>"

The assertion must carry iss and sub equal to the client id, aud equal to the configured clientAssertion.audience, a unique jti, and iat/exp inside clientAssertion.maximumLifetimeSeconds. Every jti is accepted once; presenting the same assertion twice is refused.

The mint token subcommand builds and sends that request for local testing. It is a client tool: it signs with the caller’s own key and never touches Registry Mint’s signing key.

Terminal window
mint token --url https://mint.example.org/token \
--client-id health-desk --key ./dev/health-desk.jwk

It prints the access token alone on stdout, so TOKEN=$(mint token ...) is the whole usage.

To build the same request from an application rather than a terminal, see Request an access token from your own code, which has worked Python and TypeScript examples.

Request a token and confirm the response shape:

Terminal window
curl -sS https://mint.example.org/token \
-d grant_type=client_credentials \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode "client_assertion=<compact-jws-client-assertion>"
{
"access_token": "SYNTHETIC_FIXTURE_TOKEN",
"token_type": "Bearer",
"expires_in": 300
}

Confirm the published key set resolves at the configured signing.jwksPath (default /.well-known/jwks.json), and that GET /ready returns success once at least one client is registered, the audit writer is healthy, and the signing provider passes its self-test.

Verify the retained keyed chain with the same configuration and audit key:

Terminal window
mint verify-audit --config /etc/mint/mint.yaml

The command reports the record count and keyed chain tail, and exits unsuccessfully if retained records were corrupted or reordered.

SymptomCauseFix
The token request fails with 401 invalid_clientRegistry Mint collapses every client authentication failure, an unknown client id, a bad signature, a replayed jti, an expired assertion, into this one code, so the endpoint cannot be used to probe which client ids are registered.Check the client id, the signing key, the assertion’s iat/exp, and that the jti has not already been used.
The token request fails with 400 unsupported_grant_typegrant_type is missing or is not exactly client_credentials.Send grant_type=client_credentials in the form body.
mint check or mint serve refuses to start over signingThe active public key is not an ES256 P-256 JWK with its RFC 7638 kid, a revoked key is published, or the signer cannot prove it matches the governed active key.Correct the governed key set and signer configuration. In strict mode, restore the local Transit proxy and its pinned key version.
mint serve refuses to start over auditThe audit key, directory, chain, or lock file is unsafe, another writer holds the chain, or retained records do not verify.Check owner-only permissions, run one writer per audit.path, then run mint verify-audit before deciding whether recovery is needed. Do not discard the chain to make startup pass.
mint check refuses the configuration over auditThe audit hash key file is missing, is not owner-only, or is too short. check does not open the chain, so it never reports a running writer as a fault.Restore the key file with owner-only permissions. Use mint verify-audit for the chain itself.
The token request fails with 500 server_error and readiness changes to 503Mint could not durably append the audit decision and permanently poisoned the writer for this process.Stop traffic, restore writable durable storage, preserve and verify the retained chain, then restart Mint. The failed request did not receive an access token.
Evidence Gateway rejects a token that Registry Mint mintedaccessTokens.claims on Registry Mint and the resource server’s own claim-name configuration name different claims for the same authority field.Align every claim name (principal, requesterTags, evidenceAudience, grantId, grantAuthority, and actor where used) between the two configurations.
GET /ready returns 503No client is currently registered, the client registry failed to load, the audit writer is poisoned, or the signing provider is unavailable.Check startup or reload diagnostics and audit storage. Add a valid client, restore and verify audit storage, or restore the Transit proxy and pinned version. Provider readiness recovers after a successful self-test.