Preview release. These docs are a work in progress. Pages are still being written, links may break, and structure may shift without notice. Treat everything here as a draft and report issues on GitHub.
Authorize callers to Registry Relay
Registry Relay runs in exactly one auth mode at a time. This page covers both supported modes: API-key auth and OIDC resource-server auth. Relay owns the auth config shape, scope semantics, and route enforcement; shared parsing, fingerprinting, and OIDC verification helpers come from Registry Platform. Use it when you need to configure callers for a new deployment or rotate credentials in an existing one.
When to use this
Section titled “When to use this”Use API-key mode when you control both sides of the integration and do not have an existing OpenID Connect / OAuth 2.0 (OIDC/OAuth2) Identity Provider (IdP). Use OIDC mode when callers already hold JWTs issued by a central IdP and you need to integrate Registry Relay into that trust fabric.
A given deployment runs in exactly one mode; mixed-mode operation is not supported. Source: docs/configuration.md OIDC section.
Prerequisites
Section titled “Prerequisites”- A running or buildable Registry Relay instance (see Run Registry Relay locally for setup).
- Access to the runtime config file (
config.yaml). - Access to the deployment secret store where environment variables are set.
- For OIDC mode: a configured OIDC/OAuth2 IdP with a machine client (service account) that can perform client credentials grants.
Part 1: API-key auth
Section titled “Part 1: API-key auth”How it works
Section titled “How it works”Each API key has three parts: a stable id, an environment variable name (hash_env)
that holds the SHA-256 fingerprint of the raw key, and a list of scopes.
Registry Relay loads fingerprints from environment variables at startup. It hashes any presented raw key with SHA-256 and compares against the loaded fingerprints. The raw key is never stored in YAML or logged. Source: docs/configuration.md API keys section.
When both Authorization: Bearer and X-Api-Key are present, Authorization wins.
Source: docs/api.md authentication section.
1. Generate a raw key and its fingerprint.
RAW_KEY="$(openssl rand -base64 32)"FINGERPRINT="sha256:$(printf '%s' "$RAW_KEY" | shasum -a 256 | awk '{print $1}')"printf 'raw key: %s\n' "$RAW_KEY"printf 'fingerprint: %s\n' "$FINGERPRINT"Give the raw key only to the authorized caller.
Store the fingerprint in your deployment secret store under a descriptive name,
for example INTAKE_SERVICE_API_KEY_HASH.
2. Add the key to the config.
auth: mode: api_key api_keys: - id: intake_service hash_env: INTAKE_SERVICE_API_KEY_HASH scopes: - social_registry:metadata - social_registry:rowshash_env is the environment variable name, not the value.
The value in that variable must start with sha256: followed by 64 lowercase hex characters.
Source: docs/configuration.md API keys section.
3. Export the fingerprint environment variable and restart.
export INTAKE_SERVICE_API_KEY_HASH='sha256:<64 lowercase hex chars>'just runKeyring changes take effect only after a process restart. Live keyring reload is not supported in v0. Source: docs/ops.md key provisioning section.
4. Assign the narrowest scope that lets the caller do its job.
| Scope suffix | Allows |
|---|---|
metadata | Catalog, dataset summaries, entity schema, and OpenAPI visibility for that dataset |
rows | Entity collection, single-record, and relationship reads |
evidence_verification | Submit claims for identity verification against configured evidence offerings |
aggregate | Aggregate discovery and configured aggregate execution |
admin | Admin listener operations (reload, metrics) |
Source: docs/api.md scopes table.
Part 2: OIDC resource-server auth
Section titled “Part 2: OIDC resource-server auth”How it works
Section titled “How it works”In OIDC mode, callers send a bearer JWT.
Registry Relay validates it against the configured IdP’s JWKS:
it checks standard claims (iss, aud, exp, optional nbf), looks up the signing
key by kid, and verifies the signature.
The principal_id is taken from the token’s sub claim (preferred), then client_id,
then azp.
Source: docs/api.md OIDC bearer JWT section.
1. Configure the OIDC block.
auth: mode: oidc oidc: issuer: https://idp.example.gov audience: - registry-relay discovery_url: https://idp.example.gov/.well-known/openid-configuration algorithms: - RS256 jwks_cache_ttl: 10m leeway: 60s scope_claim: scope scope_map: "role:social-registry-reader": "social_registry:rows" "role:social-registry-metadata": "social_registry:metadata"Supply exactly one of discovery_url or jwks_url.
Configs that supply both or neither are rejected at startup.
Source: docs/configuration.md OIDC field table.
A complete drop-in OIDC config targeting a local Zitadel IdP is available at
config/example.oidc.yaml.
2. Map IdP roles to Relay scopes with scope_map.
If the IdP issues tokens with role names that differ from Registry Relay’s
<dataset_id>:<level> scope format, use scope_map to rename them before the
scope check.
Registry Relay accepts scopes as a space-separated string, a JSON array, or an object
whose keys are scope names (Zitadel format); scope_claim names the JWT field to read.
Source: docs/configuration.md scope_claim and scope_map.
3. Restart with the OIDC config.
./target/release/registry-relay --config config/example.oidc.yamldiscovery_url triggers a single discovery fetch at startup to resolve jwks_uri.
A failure here aborts the process so you see the IdP wiring problem immediately.
Source: docs/configuration.md discovery vs explicit JWKS.
4. Mint a token from the IdP and call a protected endpoint.
TOKEN="$(./scripts/mint-zitadel-token.sh)" # or your IdP's equivalent
curl -si \ -H "Authorization: Bearer $TOKEN" \ http://127.0.0.1:8080/metadata/catalogFor OIDC testing against a local IdP, the bundled Zitadel compose stack in
scripts/mint-zitadel-token.sh and config/example.oidc.yaml is the fastest path to
a working end-to-end setup.
Verification
Section titled “Verification”Confirm a protected route returns data with a valid credential
Section titled “Confirm a protected route returns data with a valid credential”API-key example:
curl -si \ -H "Authorization: Bearer $INTAKE_SERVICE_API_KEY" \ http://127.0.0.1:8080/datasets/social_registry/individualExpect 200 with a JSON body.
Confirm a request without credentials is rejected
Section titled “Confirm a request without credentials is rejected”curl -si http://127.0.0.1:8080/datasets/social_registry/individualExpect 401 with a Problem Details body:
{ "type": "https://data.example.gov/problems/auth/missing_credential", "title": "Missing credential", "status": 401, "code": "auth.missing_credential"}Source: docs/api.md problem details section.
Confirm a key without the required scope is rejected
Section titled “Confirm a key without the required scope is rejected”The statistics_office key has only metadata and aggregate scopes.
Calling a rows endpoint with it must return 403:
curl -si \ -H "Authorization: Bearer $STATS_OFFICE_API_KEY" \ http://127.0.0.1:8080/datasets/social_registry/individualExpect 403 with code: "auth.scope_denied" and detail naming the required scope.
Troubleshooting
Section titled “Troubleshooting”401 auth.missing_credential: The request has no Authorization header and no
X-Api-Key header.
Add one.
401 auth.malformed_credential: The Authorization header is present but the
scheme is wrong or the bearer token is empty or unparseable.
Confirm the header value is Bearer <token> with a space.
401 auth.token_expired: The JWT exp claim is in the past (after the configured
leeway).
Mint a fresh token.
401 auth.kid_unknown: The JWT’s kid header is absent from the JWKS even after
one refresh.
Confirm the IdP is publishing the key and that discovery_url or jwks_url points
to the correct endpoint.
403 auth.scope_denied: The credential is valid but the principal does not have
the scope required by the endpoint.
Add the scope to the key’s scopes list (API-key mode) or to the token’s claims via
scope_map (OIDC mode), then restart.
401 after rotating an API key: Keyring changes require a process restart.
The old fingerprint remains active until the process is restarted with the new config.
Source: docs/ops.md key rotation section.
OIDC: 503 auth.jwks_unavailable: The JWKS endpoint is unreachable.
Registry Relay cannot verify any token in this state.
Check IdP availability and network policy.
Granular OIDC failure codes are documented in the
auth failure-code table
in docs/configuration.md.