Skip to main content

How it works, end to end

This page walks one request through the whole system, then shows what happens when things go wrong. By the end you will understand the full life of a proof-bound request.

The happy path

Step by step, in plain words:

  1. The caller asks the broker for permission to make a specific call — GET /orders for audience acme.demo.orders.
  2. The broker checks identity. In the simplest mode it just uses a local software key. On Kubernetes it can verify the caller's SPIFFE/SPIRE X.509-SVID first.
  3. The broker returns a passport + proof headers. The proof (transcript-v1) is bound to method GET, path /orders, the audience, a nonce, the body hash, etc.
  4. The caller sends the real request to the adapter, carrying the passport and proof.
  5. The adapter resolves trust material and policy for /orders from local files.
  6. The adapter runs the decision pipeline (order matters — see below).
  7. Only then does the adapter forward the request to the upstream, adding context headers like X-TrustPlane-Subject. The upstream returns business JSON.

The decision pipeline (and why order matters)

The verifier evaluates checks in a deliberate order. Cheap, policy-level denials happen before replay state is consumed, so an attacker cannot burn a victim's jti by sending junk:

Why this ordering is a security property

All the policy checks (route, source, freshness, provenance/context) happen before the replay Consume. That means a malformed or unauthorized request is rejected without spending the caller's one-time jti. Replay state is only consumed on an otherwise-valid presentation.

What every failure looks like

Denials return a stable, machine-readable reason. You will see these in adapter JSON responses and audit events:

What went wrongReason code
No route in the policy bundle for this method/pathbundle_route_missing
No policy bundle loaded at allbundle_policy_missing
Bundle too stale for a realtime/bounded routestale_bundle_fail_closed
Unknown/invalid freshness classbundle_freshness_unknown
Issuer not allowed for this routesource_issuer_mismatch
Trust domain not allowedsource_trust_domain_mismatch
Subject not allowedsource_subject_mismatch
Key not strong enough (e.g. needs attested_workload)insufficient_key_binding
Required provenance/context missing or wrongmissing_provenance, provenance_mismatch, missing_context, context_mismatch
Proof signature malformed / does not verifyinvalid_request_proof
Request does not match the signed transcript (tampered path/nonce/route)request_binding_mismatch
Same passport/proof presented twicejti_replay
(Adapter HTTP) missing Authorization401
(Adapter HTTP) wrong audience403

Where verification can happen

The same passport + proof semantics work at any enforcement point:

That portability is the point: you do not have to rewrite your API to adopt TrustPlane Auth. The most common starting point is the brownfield adapter — covered in depth in Deployment overview.

Local-first, offline-capable

Notice what is not in the happy path: there is no mandatory call to a central server during verification. The verifier only needs public trust material and route policy, both of which are local files. This is why TrustPlane Auth can protect APIs at the edge, in air-gapped environments, and in CI — and why a single bundle file can authorize many issuers and clients.

Next: get hands-on → Quickstart.