Verify caller credentials with VP evidence

Gate a policy on a cryptographically verified issuer and claims, not just a bearer token, without re-implementing verification yourself.

This guide enables a surface’s VP Evidence block, so an inbound verifiable presentation is cryptographically verified and its holder, issuer, and claims are injected into policy input as identity_binding, ready for a Rego rule to gate on. For the conceptual model, see OPA policies →.

A JWT bearer token proves a caller holds a token signed by whoever issued it; it says nothing about a separate, cryptographically verifiable credential, a verifiable presentation (VP), that a caller may carry on top of that, for example a trust-fabric-issued credential proving the caller is a recognised agent. Without VP evidence enabled, input.identity_binding is never populated, so a policy has no way to gate on it.

Use this guide when:

  • Callers present a verifiable presentation alongside or instead of a bearer token, and a policy needs to check its issuer or claims.
  • You want a belt-and-braces gate that denies outright when no VP is presented, independent of whatever the policy itself decides.
  • You are integrating with another Trust Fabric appliance or an agent that carries a VC-based identity.

You do not need this if your callers authenticate only with a JWT, API key, or mTLS certificate with no separate verifiable presentation involved.

Prerequisites

Steps

Enable VP evidence on the surface

The surface's VP evidence block with the Evaluate VP evidence toggle turned on

Under SURFACES in the dashboard sidebar, select LLM, open your surface, select its Surface node (the root container the rest of the pipeline sits inside), and select Configure Surface Features in its side panel. Scroll to VP evidence and turn on Evaluate VP evidence.

Choose where the VP travels

Set VP carried in to Request header (the default) or JSON body field, matching how your callers actually attach the presentation. For the header option, confirm Header name matches what callers send; it defaults to X-Agent-Stream-Presentation.

Decide whether to require one outright

The VP evidence block with VP carried in set to Request header, the Header name field, and the Deny when no VP is presented toggle

Turn on Deny when no VP is presented to deny a request outright when no VP is presented at all, before the surface’s policy even runs. Leave it off if a policy should decide case by case, including for callers with no VP.

Gate your policy on the verified identity

Update your policy’s Rego to check input.identity_binding, populated only once a presented VP verifies successfully:

package llm.policy

default allow = false

trusted_issuers := {
  "did:web:issuer.example.com",
}

allow if {
  input.identity_binding.verified
  trusted_issuers[input.identity_binding.issuer_did]
}

Save the surface

Select the save icon in the toolbar. The change applies immediately; no restart is required.

Confirm

Replace <YOUR_APPLIANCE_HOST> and <YOUR_SURFACE_ROUTE> with your surface’s values.

Test 1: a request with a VP from a trusted issuer returns 200

curl -k -X POST "https://<YOUR_APPLIANCE_HOST><YOUR_SURFACE_ROUTE>/v1/chat/completions" \
  -H "X-Agent-Stream-Presentation: <VALID_VP_JWT>" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      { "role": "user", "content": "Say hello." }
    ]
  }'

The -k flag disables TLS certificate verification. Use this for local testing only. Remove it in production.

Expected: 200 OK. The VP verified successfully, its issuer matched the policy’s allow-list, and identity_binding.verified was true.

Test 2: a request with no VP is denied before the policy runs, when Deny when no VP is presented is on

Resend the same request with the X-Agent-Stream-Presentation header removed. Expected: denied before the policy even runs, since Deny when no VP is presented is a gate on top of whatever the policy decides.

Troubleshooting

SymptomLikely causeFix
input.identity_binding is always absent, even though the caller attaches a VP.VP evidence is not enabled, or VP carried in/Header name does not match what the caller actually sends.Confirm Evaluate VP evidence is on, and that the header name (or body location) matches the caller’s request exactly.
Every request is denied, not just ones without a valid VP.Deny when no VP is presented is on, but callers have not yet been issued a VP to present.Turn off Deny when no VP is presented while onboarding callers, or issue VPs to every caller first.
A request from what should be a trusted issuer is still denied by the policy.The VP’s issuer is not on the policy’s allow-list, or the VP itself failed verification, so identity_binding.verified never became true.Confirm the issuer DID is listed in the policy, and that the presented VP is not expired or malformed.
identity_binding is present but verified is false.The VP was received but failed cryptographic verification.This is expected: a policy checking input.identity_binding.verified correctly denies an unverifiable presentation rather than trusting its claims blindly.

Next steps