Documentation preview. These pages target Registryctl v0.15.0, which is not published yet. Public download commands will not work until that release exists. For runnable released instructions, use v0.13.0.
Self-attestation lets a citizen use their own OIDC token to evaluate or render only the claims that policy allows. It can authorize credential issuance only when every credential claim is registry-backed and the stored evaluation retains the exact compiler-pinned Relay execution provenance. Source-free claims are evaluation-only. The subject is bound to the token. This guide is for operators configuring that flow with an identity provider and relying-party or wallet clients. Registry-backed citizen evidence requires a separately compiled Relay consultation and service policy.
Use oid4vci-wallet-interop.md when the caller is
a wallet using the OID4VCI facade. Use this guide for the shared
self-attestation policy that sits underneath wallet and non-wallet citizen
flows.
Security goal
Section titled “Security goal”The core guarantee is that a citizen token can only be used for the exact self authorized by policy, and only for explicitly allowed claims, purposes, formats, disclosures, and credential profiles.
The credential-profile allow-lists apply only to registry-backed claims. They
do not turn a self_attested claim into issuable evidence.
Notary validates the token, checks client and audience policy, checks subject binding, checks scopes and operation allow-lists, then evaluates only the configured subject-bound claims.
flowchart TD
Tok["Citizen OIDC token"] --> V{"Validate token<br/>issuer, audience, algorithm, lifetime"}
V -- "ok" --> C{"Client and audience policy"}
C -- "ok" --> S{"Subject binding<br/>derive subject from token claim"}
S -- "ok" --> Sc{"Scopes and operation allow-lists"}
Sc -- "ok" --> Bound["Evaluate configured subject-bound claim"]
Bound --> Op
Op["Evaluate or render within the allow-lists"]
V -- "fail" --> X["Reject before evaluation"]
C -- "fail" --> X
S -- "conflicting caller identity" --> X
Sc -- "fail" --> X
Self-attestation gates a request through token validation, client and audience policy, subject binding, and scope and operation allow-lists. Any gate failure rejects the request before claim evaluation.
For /v1/evaluations, citizen callers do not need to send their own target
identity. Registry Notary derives requester, target, and
relationship: self from the verified subject-binding token claim. Conflicting
caller-supplied identity context is rejected before claim evaluation.
When to use it
Section titled “When to use it”Use self-attestation when:
- A citizen portal evaluates allowed evidence claims for the token-bound subject before the evidence consumer applies its own eligibility or decision policy.
- A wallet flow issues a credential for the token-bound subject from a compiler-pinned Relay-backed evaluation.
- The identity provider can provide a stable, reviewed subject-binding claim.
- The evidence service accepts token-bound subject access for the configured purpose.
Self-attestation authorizes subject-bound access to configured evidence. The identity token is not evidence of consumer eligibility, and Registry Notary does not turn it into a consumer-owned outcome.
Do not use it when:
- The token has no trustworthy subject identifier.
- The same endpoint needs to evaluate arbitrary subjects for a case worker or service. Use machine auth for that.
- Claims require batch evaluation. Batch evaluation is not supported for self-attestation.
- The source owner has not approved citizen-token driven access.
- A Registry-backed claim cannot use a compiler-pinned Relay consultation whose inputs are bound to the authenticated requester or target identifiers.
Identity provider requirements
Section titled “Identity provider requirements”Before enabling the flow, confirm with the identity-provider owner:
- Access tokens are JWTs Notary can verify through a JWKS URL.
- Tokens have a stable issuer and audience.
- The wallet or citizen client has a stable client id or audience.
- The token carries a claim that exactly identifies the registry subject.
- The token signing algorithm is explicit and stable. Configure the algorithm
your provider actually uses, such as
EdDSAorRS256, and do not mix symmetric and asymmetric algorithms in one deployment. RS256 JWKS keys must use a 2048-8192-bit RSA modulus. - Token lifetime, auth age, assurance, and clock skew can satisfy your policy.
- External scopes can be mapped to the Notary scopes you require.
Avoid using sub as a civil identifier unless the identity-provider owner has
confirmed it is the right identifier for source lookups. If you do use sub,
set allow_sub_as_civil_id: true so the config records that decision.
OIDC auth config
Section titled “OIDC auth config”Self-attestation requires an auth.oidc block. There is no authentication mode selector:
auth: oidc: issuer: https://idp.example.gov jwks_url: https://idp.example.gov/.well-known/jwks.json audiences: - registry-notary-citizen allowed_clients: - citizen-portal allowed_algorithms: - EdDSA allowed_token_types: - JWT scope_claim: scope scope_separator: " " scope_map: citizen.attest: - registry_notary:self_attest principal_claim: sub leeway: 60sThe auth block is additive, so OIDC can coexist with static api_keys for
machine clients. Static bearer_tokens cannot coexist with OIDC because both
use the Authorization: Bearer transport. Keep machine credentials out of a
citizen-facing deployment unless a reviewed use case requires both, and grant
every credential only its exact scopes.
Subject binding
Section titled “Subject binding”Subject binding is the most important part of the config:
subject_access: subject_binding: token_claim: civil_id claim_source: access_token request_field: subject_id id_type: UIN normalize: exactRules:
token_claimmust be present in the configured token source.claim_sourceisaccess_tokenby default. Useuserinfoonly whenauth.oidc.userinfo_endpointis configured and reviewed. The pre-authorized-code flow resolves the same binding claim from its own RP login, so whenclaim_source: userinfoit additionally requiresoid4vci.pre_authorized_code.esignet.userinfo_url(the callback fetches the userinfo JWS with the eSignet access token).request_fieldis currentlysubject_id.id_typeshould match the source lookup identifier type.normalizemust beexact.
Exact matching is deliberate. Do not rely on case folding, punctuation removal, or local identifier normalization unless that behavior is implemented and reviewed as part of the product.
Citizen client policy
Section titled “Citizen client policy”Restrict which OIDC clients can use the flow:
subject_access: citizen_clients: allowed_client_ids: - citizen-portal allowed_audiences: - registry-notary-citizenAt least one client id or audience is required. Any allowed audience must also
appear in auth.oidc.audiences. If auth.oidc.allowed_clients is nonempty,
each self-attestation client id must also be listed there.
Token policy
Section titled “Token policy”Set explicit policy ceilings:
subject_access: token_policy: required_acr_values: - urn:example:loa:substantial assurance_claim_source: access_token max_auth_age_seconds: 600 max_access_token_lifetime_seconds: 900 max_evaluation_age_seconds: 300 max_credential_validity_seconds: 31536000 max_clock_leeway_seconds: 60Guidance:
- Keep access-token lifetime short for public citizen flows.
- Keep evaluation age short so a registry-backed credential is issued from fresh evidence.
- Set credential validity to the period the issuing agency wants verifiers to accept the wallet-held VC. Use credential status or another lifecycle surface for long-lived credentials.
- Keep clock leeway small and ensure
auth.oidc.leewaydoes not exceedmax_clock_leeway_seconds. - Use
required_acr_valueswhen the identity provider can represent assurance level reliably.
Allowed operations and claims
Section titled “Allowed operations and claims”Every self-attestation surface is allow-listed:
subject_access: allowed_operations: evaluate: true render: false issue_credential: true batch_evaluate: false allowed_purposes: - wallet_credential_issuance allowed_claims: - birth-record-exists allowed_formats: - application/vnd.registry-notary.claim-result+json allowed_disclosures: - value - redacted credential_profiles: - birth_record_sd_jwtRules:
- Enable only operations the citizen flow actually needs.
issue_credential: truerequires every allowed claim and credential-profile claim to useregistry_backed. Useissue_credential: falsefor a source-free service.- Include the canonical claim-result JSON format for the internal evaluation
that backs issuance. The named credential profile separately selects
application/dc+sd-jwtas the credential output. batch_evaluatemust remain false; batch evaluation is not supported.allowed_claimsmust reference existing claims.credential_profilesmust reference existing profiles.- Credential profiles must use DID holder binding, proof of possession, and
did:jwk. - Claims and profiles must agree that the credential profile can issue that claim.
Delegated self-attestation
Section titled “Delegated self-attestation”Delegated subject access is available only when
subject_access.delegation.enabled: true and
subject_access.delegation.allowed_relationships contains the requested
relationship. Each allowed relationship names one compiler-pinned Relay-backed
proof_claim and its exact claims, purposes, formats, disclosures, and
no credential profiles. Delegated self-attestation is evaluation and rendering
only in 1.0. Both /v1/credentials and the OID4VCI credential endpoint reject
delegated evaluations. Keep delegation disabled unless that relationship proof
and its scoped authorization-details contract have been reviewed.
Configuration load rejects a delegated relationship credential_profiles
entry or a credential-profile binding on a delegated claim. To keep delegated
evaluation, remove that credential capability. To issue a credential, model a
separate registry-backed, non-delegated claim and bind it through
subject_access.credential_profiles.
Scope policy
Section titled “Scope policy”Use scope policy to require citizen tokens to carry an explicit permission:
subject_access: scope_policy: required required_scopes: - registry_notary:self_attestscope_policy values:
required: required scopes must be present.optional: scopes may be present, but policy still records what is expected.disabled: no required scopes;required_scopesmust be empty.
Prefer required for shared or public deployments. Use disabled only for
controlled demos where client and audience policy are sufficient.
Wallet origins
Section titled “Wallet origins”For browser-based wallets or portals, list exact HTTPS origins:
subject_access: allowed_wallet_origins: - https://wallet.example.govWildcards are rejected. HTTP origins are rejected. Empty origins are acceptable for non-browser or backend-mediated flows where CORS is not part of the path.
Rate limits
Section titled “Rate limits”Rate-limit counters use the configured Notary state plane. PostgreSQL shares the counters across replicas; explicit local in-memory state keeps them in the process:
subject_access: rate_limits: invalid_token_per_client_address_per_minute: 20 per_principal_per_minute: 30 subject_mismatch_per_principal_per_hour: 5 per_holder_per_hour: 20 credential_issuance_per_principal_per_hour: 10All values must be greater than zero. These application limits are guardrails, but public deployments should also use gateway and identity-provider controls.
Evidence and purpose review
Section titled “Evidence and purpose review”self_attested claims perform no Relay or registry-source I/O.
registry_backed claims use only their exact compiler-pinned consultations.
Confirm that:
- every
self_attestedclaim and dependency is source-free; - no
self_attestedclaim appears inallowed_claims, a credential profile, or an OID4VCI projection when credential issuance is configured; - every
registry_backedclaim maps Relay inputs only from the authenticated requester or target identifiers; - claim and request purposes are stable and auditable;
- caller scopes, client ids, audiences, formats, disclosures, and any registry-backed credential profiles are narrowly allow-listed; and
- the evidence service does not present self-attestation as registry-verified evidence; and
- the evidence consumer determines how evidence is used, and the decision owner, not Registry Notary, remains accountable for eligibility, qualification, entitlement, prioritization, approval, referral, payment, workflow, and action decisions.
Use source-claim-modeling-guide.md to
review the evidence boundary.
Rollout checklist
Section titled “Rollout checklist”- OIDC issuer, JWKS, audience, and client id are stable.
- The subject-binding claim is reviewed and present in test tokens.
normalize: exactis acceptable for the identifier format.scope_policy: requiredis used unless there is a documented reason not to.- All allow-lists are narrow and reference existing claims and profiles.
- Source-free services set
issue_credential: falseand configure no credential profiles or OID4VCI credential configurations. - Batch is disabled.
- Credential profiles use DID holder binding with proof of possession.
- Wallet origins are exact HTTPS origins, or empty for non-browser flows.
- Gateway and identity-provider rate limits are in place.
- PostgreSQL correctness state is installed when multiple processes can serve holder-proof or nonce traffic.
doctorpasses, then a controlled self-attestation test passes with a test subject.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Check |
|---|---|---|
| Config validation fails | OIDC is missing or a static bearer token is also configured | auth.oidc, auth.bearer_tokens |
| Token rejected | Issuer, audience, client id, algorithm, or scope mismatch | Token header and claims, auth.oidc, scope_map |
| Subject mismatch | Token claim is missing or caller-supplied identity context conflicts with the derived subject | subject_binding.token_claim, token claims, request body identity fields |
| Userinfo subject not found | claim_source: userinfo without a usable endpoint or issuer | auth.oidc.userinfo_endpoint, userinfo_issuers |
| Delegation config rejected | Delegated authorization does not match the compiled service policy | Check requester, target, relationship, purpose, and authorization details |
| Delegated credential config rejected | A delegated relationship or delegated claim still carries a credential-profile binding | Remove the delegated credential capability, or use a registry-backed non-delegated credential claim |
| Credential config rejected | A source-free claim or mixed profile was exposed for issuance | Use only mutually bound registry_backed claims in allowed_claims, credential profiles, and OID4VCI projections |
| Credential issuance denied after upgrade | The evaluation is legacy, source-free, or its stored Relay execution pins do not match | Re-evaluate the exact registry-backed claims under the active configuration |
| Batch request denied | Batch evaluation is not supported for self-attestation | Keep batch_evaluate: false |
| Works locally but fails active-active | state.storage: in_memory is process-local | Add gateway limits and install PostgreSQL correctness state |