Skip to content
Registry StackDocsDevelopment (unreleased)

Request Evidence from your application

For the consumer or verifier

View as Markdown

If you build an application that asks Registry Evidence for an assertion, you give it its own client identity, request one assertion through the maintained Python client, decide from the verified value, and keep what you need to stand behind that decision later.

Outcome
An application that decides from a verified assertion and keeps it for replay.
Time
About 15 minutes
Level
Local development with synthetic data
Prerequisites
The project from Your first assertionDocker, Python 3.11 or later, and optionally Node.js

You continue the adult-status project from Your first assertion, with its synthetic source answering on port 8081. If you stopped it, start it again in another terminal:

Terminal window
python3 serve-source.py \
--port 8081 \
--token-file .generated/secrets/synthetic-source-token

The deployment’s operator registers your application as a client under an access profile. Here you play the operator, from the project directory. Registry Evidence reads its clients only as it starts, and rstack evidence refuses a client change while it runs, so stop it first:

Terminal window
rstack evidence dev stop .
Local Evidence stopped

Register the application:

Terminal window
rstack evidence access client add age-check-app \
--access-profile standalone-service \
--generate-local-key
Added client age-check-app with access profile standalone-service.

The client’s private key stays owner-only at .evidence/clients/age-check-app/private.jwk. Start Registry Evidence again with the same ports, so it loads the client:

Terminal window
rstack evidence dev start . \
--target local-target.yaml \
--evidence-port 18080 \
--issuer-port 8082
Evidence ready at http://127.0.0.1:18080
Issuer ready at http://127.0.0.1:8082

A client profile holds everything your code would otherwise hard-code about the deployment:

Terminal window
rstack evidence client profile create \
--base-url http://127.0.0.1:18080 \
--client-id age-check-app \
--private-key-file .evidence/clients/age-check-app/private.jwk \
--local-loopback-discovery \
--client-assertion-audience http://127.0.0.1:8082 \
--resource urn:registrystack:evidence:local:gateway \
--scope evidence:request \
--expected-audience urn:example:audience:standalone-app \
--expected-issuer urn:registrystack:evidence:local:issuer \
--expected-provider urn:registrystack:evidence:local:provider \
--output age-check-client.json
Created client profile

It pins the base URL, issuer, provider, and audience the client insists on, and the token request it makes (Request an access token explains those values). It refers to the private key without containing it. For a deployed service the operator gives you each value; --local-loopback-discovery is for loopback only.

Install registry-stack-client at the version rstack evidence --version prints:

Terminal window
python3 -m venv .venv
. .venv/bin/activate
python -m pip install "registry-stack-client==<version>"

Save this program as request_adult_status.py:

request_adult_status.py
import time
from pathlib import Path
from registry_client.evidence import EvidenceClient
client = EvidenceClient.from_profile("age-check-client.json")
verified = client.request("adult-status", **{"record-id": "adult"})
decided_at = int(time.time())
Path("adult-status.response.jws").write_bytes(verified.assertion)
Path("adult-status.retained.json").write_bytes(verified.retained_verification)
Path("adult-status.verified-at.txt").write_text(f"{decided_at}\n")
if verified.values["adult"] is not True:
raise RuntimeError("not an adult")
print("VERIFIED adult=true")

from_profile reads the client profile. The first argument of request is the assertion definition’s handle, and each selector is a keyword argument (record-id is spelled as a dictionary because of its hyphen). request gets a token, sends once, and verifies the signed response before it returns, so verified.values is the only thing to decide from.

The program keeps three files:

  • adult-status.response.jws, the signed assertion exactly as received.
  • adult-status.retained.json, the retained verification: the keys and expectations fixed before the request was sent, without selector values.
  • adult-status.verified-at.txt, the decision instant in Unix seconds.

Run it:

Terminal window
python3 request_adult_status.py
VERIFIED adult=true

With @registrystack/client at the same version, this writes the same three files and prints the same line. Run one program, not both.

request_adult_status.js
const fs = require('node:fs');
const { evidence } = require('@registrystack/client');
(async () => {
const client = evidence.EvidenceClient.fromProfile('age-check-client.json');
const result = await client.request({
handle: 'adult-status',
selectors: { 'record-id': 'adult' },
});
const decidedAt = Math.floor(Date.now() / 1000);
fs.writeFileSync('adult-status.response.jws', result.assertion);
fs.writeFileSync('adult-status.retained.json', result.retainedVerification);
fs.writeFileSync('adult-status.verified-at.txt', `${decidedAt}\n`);
if (result.values.adult !== true) {
throw new Error('not an adult');
}
console.log('VERIFIED adult=true');
})();
Terminal window
node request_adult_status.js

The audit view reads a stopped session, so stop Registry Evidence first:

Terminal window
rstack evidence dev stop .
rstack evidence audit show --last-operation
Local Evidence stopped
ACCESS AUTHORIZED adult-status age-check requester=<pseudonym>
DISCLOSURE RELEASED adult

The gateway recorded who asked, for which purpose, and which value it released, under a pseudonym rather than your client identifier. Press Ctrl+C in the source’s terminal. Keep the project and the three files.

Verify an assertion as a consumer