Registry stack documentation: machine-readable Markdown.
Index of all pages: https://docs.registrystack.org/dev/llms.txt
Full corpus: https://docs.registrystack.org/dev/llms-full.txt

# Configure Registry Mint

> Configure Registry Mint to issue short-lived, audience-bound access tokens to registered clients when a deployment has no identity provider.

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.

## When to use Registry Mint

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](../../reference/mint/#how-a-client-registry-mint-and-evidence-gateway-interact).

## When not to use Registry Mint

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.

## Before you start

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.

## Configure the deployment

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.

```yaml
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](../../tutorials/move-evidence-to-production-signing/)
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](../../reference/mint/).

## Register a client

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

```yaml
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:

```sh
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`.

## Start Registry Mint

```sh
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.

## Obtain a token

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.

```sh
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.

```sh
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](../request-an-access-token/), which has worked
Python and TypeScript examples.

## Verify the deployment

Request a token and confirm the response shape:

```sh
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>"
```

```json
{
  "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:

```sh
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.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| The token request fails with `401 invalid_client` | Registry 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_type` | `grant_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 signing | The 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 audit | The 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 audit | The 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 `503` | Mint 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 minted | `accessTokens.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 `503` | No 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. |