Skip to content
Registry StackDocsv0.34.0

Control who can request Evidence Gateway

For the assertion provider

View as Markdown

Complete Return a governed value before starting this tutorial. You will give two applications different access to that Evidence Gateway service, add one while the service is running, and prove that a valid access token does not let its client ask every configured question.

Outcome
Two applications receive different policies, with generation-bound onboarding, refusal, and revocation.
Time
About 15 minutes
Level
Local development with synthetic data
Prerequisites
The completed governed-value tutorialIts adult-status project and materialized source mockPython 3A shell with curl

This tutorial manages access for machine applications, not accounts for people.

The pinned local issuer authenticates each application from its own key and issues a short-lived access token. Evidence Gateway then checks whether the application’s access policy includes the configured question. These are separate decisions:

DecisionExampleOwner
Which application is calling?age-checkerLocal issuer client registration
What may it ask?The age-checks policy includes adult-statusEvidence Gateway access policy
May Evidence Gateway read its source?GET /people/{person_id}Evidence Gateway source configuration
May a consumer trust the result?Expected issuer, audience, and signing keyConsumer verification policy

The first two rows are the access-management boundary you will configure. Source credentials and consumer trust remain independent.

Access policies are part of Evidence Gateway’s immutable governed configuration. Client membership is part of the local issuer generation:

ChangeRuntime effect
Add or revoke a clientStop the local session, change the reviewed registry, and start a new generation
Add a question or change an access policyStart a new Evidence Gateway generation

A client change affects the next local generation. Stopping that generation also stops its issuer.

In one terminal, return to the working directory from the first tutorial and serve the checked synthetic cases:

Terminal window
evidencectl source mock serve --config adult-status/mocks/source.yaml

Leave the source mock running. In another terminal, enter the project containing both questions:

Terminal window
cd adult-status

The project has these two governed questions:

QuestionDeclared purposeAnswer
adult-statusage-checkBoolean adult status
age-bracketservice-path-selectionOne reviewed age bracket

An access policy names one or more questions. Each question already fixes its purpose, subject shape, source, and possible answers, so you do not repeat those fields in the policy.

Define a policy for each service task:

Terminal window
evidencectl access policy add age-checks --question adult-status
evidencectl access policy add service-routing --question age-bracket
Added access policy age-checks for adult-status.
Added access policy service-routing for age-bracket.

Review the governed policies before starting Evidence Gateway:

Terminal window
evidencectl access policy list

The command prints a tab-separated POLICY and QUESTIONS table, one row per policy: age-checks holding adult-status, and service-routing holding age-bracket.

The commands write the reviewable policy documents to access/policies/age-checks.yaml and access/policies/service-routing.yaml. Many clients can share one policy. A client can also belong to several policies when their question sets do not overlap. evidencectl rejects overlapping policy assignments because they would make the access decision ambiguous. Adding a question to either policy later changes the governed authorization boundary and requires a new Evidence Gateway generation.

Register age-checker under the age-checks policy:

Terminal window
evidencectl access client add age-checker \
--policy age-checks \
--generate-local-key
Added client age-checker with policy age-checks.

--generate-local-key keeps this tutorial self-contained. The private key represents the application’s identity and remains owner-only at .evidence/clients/age-checker/private.jwk. The reviewable registration at access/clients/age-checker.yaml contains the policy membership and public key, never the private key.

Compile the questions and both access policies, then start Evidence Gateway and its local issuer:

Terminal window
evidencectl dev start .
Evidence ready at http://127.0.0.1:8080
Issuer ready at http://127.0.0.1:8081

The local issuer recognizes age-checker. Evidence Gateway has both access policies available, even though no client belongs to service-routing yet.

Prepare an adult-status request as age-checker:

Terminal window
evidencectl request prepare adult-status \
--purpose age-check \
--subject person_id=person-123 \
--client age-checker \
--name age-checker-allowed

The Evidence client prepares the request and closes its pinned verification expectations locally. evidencectl separately signs a one-time client assertion with the local application key and exchanges it with the local issuer for a short-lived access token. Preparation sends no HTTP request to Evidence Gateway and performs no source access.

Send the request across the Evidence Gateway HTTP boundary:

Terminal window
curl --silent --show-error --fail-with-body \
--config .evidence/requests/age-checker-allowed/authorization.curl \
--request POST \
--url http://127.0.0.1:8080/v1/evidence \
--header 'Content-Type: application/json' \
--header 'Accept: application/jose+json' \
--data-binary @.evidence/requests/age-checker-allowed/request.json \
--output age-checker-allowed.jws.json \
--write-out 'HTTP %{http_code}\n'
HTTP 200

Verify the response before reading it:

Terminal window
evidencectl verify age-checker-allowed.jws.json \
--context .evidence/requests/age-checker-allowed/verification.json \
--output age-checker-allowed.verified.json
VERIFIED

The application was authenticated, its age-checks policy matched the request, and Evidence Gateway called the registry only after both checks passed.

Add an application for the next generation

Section titled “Add an application for the next generation”

Stop the current session, then register service-router under the existing service-routing policy:

Terminal window
evidencectl dev stop
evidencectl access client add service-router \
--policy service-routing \
--generate-local-key
Added client service-router with policy service-routing.

The command writes the public registration to access/clients/service-router.yaml, keeps the private key under .evidence/clients/, and leaves the stopped generated session unchanged. Start a fresh generation so Evidence Gateway and the issuer consume one validated snapshot:

Terminal window
evidencectl dev start .

Review the active client assignments:

Terminal window
evidencectl access client list

The command prints a tab-separated CLIENT, STATUS, and POLICIES table. Both age-checker and service-router are active, each under the single policy it was registered with.

Prepare an age-bracket request as service-router:

Terminal window
evidencectl request prepare age-bracket \
--purpose service-path-selection \
--subject person_id=person-456 \
--client service-router \
--name service-router-allowed

Send and verify the response:

Terminal window
curl --silent --show-error --fail-with-body \
--config .evidence/requests/service-router-allowed/authorization.curl \
--request POST \
--url http://127.0.0.1:8080/v1/evidence \
--header 'Content-Type: application/json' \
--header 'Accept: application/jose+json' \
--data-binary @.evidence/requests/service-router-allowed/request.json \
--output service-router-allowed.jws.json \
--write-out 'HTTP %{http_code}\n'
evidencectl verify service-router-allowed.jws.json \
--context .evidence/requests/service-router-allowed/verification.json \
--output service-router-allowed.verified.json
HTTP 200
VERIFIED

The successful token exchange proves that the new issuer generation accepted the client registry. service-router belongs to the policy required for this question. Preparation made zero Evidence Gateway HTTP requests, curl made the single POST /v1/evidence assertion request, and evidencectl verify checked the response offline.

Try a question the application was not granted

Section titled “Try a question the application was not granted”

Prepare the same age-bracket request using the age-checker identity:

Terminal window
evidencectl request prepare age-bracket \
--purpose service-path-selection \
--subject person_id=person-456 \
--client age-checker \
--name age-checker-refused

The command can prepare the request because age-bracket is a valid project question and age-checker is a registered client. The client preparation path closes the request and its verification expectations without calling Evidence Gateway or deciding whether this application may ask the question. The local issuer only issues the token. Evidence Gateway makes the authorization decision when you send the request.

Send the request:

Terminal window
curl --silent --show-error \
--config .evidence/requests/age-checker-refused/authorization.curl \
--request POST \
--url http://127.0.0.1:8080/v1/evidence \
--header 'Content-Type: application/json' \
--header 'Accept: application/jose+json' \
--data-binary @.evidence/requests/age-checker-refused/request.json \
--output age-checker-refused.json \
--write-out 'HTTP %{http_code}\n'
HTTP 403

Inspect the safe problem response:

Terminal window
python3 -m json.tool age-checker-refused.json
{
"type": "https://id.registrystack.org/problems/registry-evidence/evidence/denied",
"title": "Evidence request is not permitted",
"status": 403,
"detail": "the Evidence request is not permitted",
"code": "evidence.denied",
"traceId": "<32-lowercase-hex-trace-id>"
}

The access token is valid, but the client’s age-checks policy does not authorize the age-bracket question. Changing only the authenticated application changes the access decision. The registry terminal shows no new GET /people/person-456, because Evidence Gateway refuses the request before source access. The response does not reveal which application, question, or policy caused the refusal.

Stop the generation after the refusal and inspect its completed audit chain before replacing it:

Terminal window
evidencectl dev stop
evidencectl audit show --last-operation
Local Evidence stopped
ACCESS REFUSED requester=<pseudonym> reason=not_authorized

The privacy-safe refusal event contains a requester pseudonym and the closed not_authorized reason. It does not contain the rejected requirement, purpose, selector, authority, response format, client identifier, or access token.

Revoke age-checker while the session is stopped, then start the next generation:

Terminal window
evidencectl access client revoke age-checker
evidencectl dev start .
Revoked client age-checker.
Evidence ready at http://127.0.0.1:8080
Issuer ready at http://127.0.0.1:8081

The command updates access/clients/age-checker.yaml. The next issuer generation excludes the revoked client.

Try to prepare a fresh request as the revoked client:

Terminal window
evidencectl request prepare adult-status \
--purpose age-check \
--subject person_id=person-123 \
--client age-checker \
--name age-checker-revoked
evidencectl: unknown or revoked active client age-checker

evidencectl rejects an unknown or revoked named client locally, before contacting the issuer or publishing request artifacts. service-router remains registered and keeps its service-routing membership.

Stop the local services:

Terminal window
evidencectl dev stop
Local Evidence stopped

The revoked-client preparation remains local and creates no Evidence Gateway audit event because no HTTP request was sent.

Remove the stopped generated runtime while preserving the editable questions, access policy, and request artifacts:

Terminal window
evidencectl dev clean

Return to the registry terminal and press Ctrl+C.

  • Authentication identifies the calling application but grants no question by itself.
  • Many applications can share a governed access policy without client-specific Evidence Gateway config.
  • A client registry change takes effect only through one newly validated local generation.
  • Each application receives only the questions included in its assigned policies.
  • Evidence Gateway refuses unauthorized questions before reading the source.
  • Evidence Gateway retains a privacy-safe audit event for an authenticated authorization refusal.
  • Revoking a local client excludes it from the next issuer generation and prevents local request preparation.
  • Adding a question or changing an access policy requires a new Evidence Gateway generation.
  • Source authentication and consumer trust remain separate from caller access.