Two workloads on the internet want to talk. But how does Workload B know the request really came from Workload A — and not an attacker?
The core problem: TCP/IP packets carry no inherent identity. Anyone can claim to be anyone. A service receiving a request has zero cryptographic proof of the caller's identity.
Without identity
Any process can forge a request No way to detect impersonation Credentials get shared, leaked, stolen
WIMSE's answer
Cryptographic workload identity Short-lived, unforgeable JWT tokens Proof-of-possession per request
What you'll learn: How WIMSE uses JWT + X.509 + proof-of-possession to give each workload a verifiable identity — from first principles, powered by real Go crypto compiled to WebAssembly.
Chapter 2 of 12
WIT: Your Identity Card
The Workload Identity Token (WIT) is a signed JWT issued by a trusted Identity Provider. It carries the workload's URI-based identity and a public key for proof-of-possession.
cnf.jwk — The WIT embeds the workload's public key in the confirmation claim. A receiver can later verify that a specific request was signed by the matching private key — without any separate key lookup.
Chapter 3 of 12
mTLS: Lock the Channel
Mutual TLS adds a transport-layer identity layer: both sides present X.509 certificates with SPIFFE URI Subject Alternative Names before any data flows.
SPIFFE URI SAN — The X.509 certificate binds the workload's URI identity at the transport layer. Even before any JWT is sent, the TLS handshake proves who is connecting.
Why two identity layers? mTLS (transport) + WIT/WPT (application) gives defense-in-depth. mTLS proves the TLS session belongs to the right workload. WIT+WPT prove the specific request is authorized — even if the session is somehow compromised.
Show PEM certificate
Chapter 4 of 12
WPT: Prove Each Request
A Workload Proof Token (WPT) is a short-lived, per-request JWT signed with the workload's private key. It binds to the WIT and to the exact target URI — making token theft useless.
wth binding — The WPT embeds a cryptographic digest of the WIT. A receiver can verify both tokens came from the same workload. Stealing just the WPT is useless without the matching WIT.
jti (JWT ID) — Each WPT has a random unique identifier used for replay detection. The same WPT cannot be re-used after the server records its jti.
Chapter 5 of 12
The Validation Dance
When Workload B receives a request it runs 9 checks before letting it through. Each one protects against a specific attack class.
✓
Request authorized Caller:
Chapter 6 of 12
Fine-Grained Authorization
Authentication answers “who are you?” — Authorization answers “what are you allowed to do?” The WIT's sub and trust_domain drive fine-grained access control.
Resource:Action:
Subject Pattern
Resource
Action
Decision
Reason
Run the check to see policy evaluation
WIMSE + OPA/Cedar/OpenFGA: In production the policy engine is a dedicated service. The WIT's sub claim becomes the principal in the authorization query — no additional identity lookup needed.
Chapter 7 of 12
Selective Disclosure
An SD-WIT uses the SD-JWT standard to let a workload present only the claims a specific service needs — without re-issuance. Hidden claims are replaced with cryptographic hashes.
Privacy by design: A workload presenting to a low-trust service can reveal only its sub, hiding roles and trust_domain. The verifier still gets a cryptographically unforgeable identity — with fewer claims.
Chapter 8 of 12
Token Exchange
Workload A (cloud-a.example) needs to call Workload B (cloud-b.example). Problem: B only trusts its own IdP. A Token Exchange Service bridges the trust domains.
① Workload A POSTs WIT-A to Token Exchange Service
② Exchange validates WIT-A against IdP-A JWKS & applies trust policy
③ Exchange issues WIT-B signed by IdP-B key
④ Workload A sends WIT-B + fresh WPT to Workload B
①Workload A POSTs WIT-A (iss=cloud-a) to the Token Exchange Service at POST /token/exchange
②Exchange validates WIT-A against IdP-A's JWKS, applies cross-domain trust policy, optionally rewrites the subject principal
③Exchange issues WIT-B (iss=cloud-b) signed with IdP-B's private key — accepted natively by Workload B's validator
④A generates a fresh WPT over WIT-B using its own private key, then sends WIT-B + WPT to Workload B
No federation required
IdP-A and IdP-B don't need to know about each other. Only the Exchange Service needs bilateral trust configuration.
Subject rewriting
spiffe://cloud-a.example/svc/billing → wimse://cloud-b.example/external/billing — mapped by policy, fully auditable.
Multi-cloud in practice: This is how a workload in AWS calls a workload in GCP without a shared IdP or hard-coded credentials. The Exchange Service is the minimal, auditable trust boundary between domains.
Chapter 9 of 12
Attacks & Defenses
What happens when an attacker captures a valid WPT and tries to replay it? WIMSE's jti tracking makes replay attacks impossible — even with a perfectly valid, unexpired stolen token.
✓
1st Request — Allowed
WPT valid & fresh jti recorded in store
✗
2nd Request — Blocked
jti already seen — rejected
jti replay protection: The WPT's jti is stored on first use. Any re-use of the same token is rejected immediately — even if the signature is valid and expiry is in the future.
Full defense summary
✓
ES256 signatures — WIT + WPT are ECDSA-signed; forging is computationally infeasible with P-256
✓
Short-lived tokens — WIT: 1h, WPT: 5 min; stolen tokens expire before they can be exploited
✓
jti replay prevention — Each WPT has a unique random ID; replays are detected and rejected immediately
✓
wth binding — WPT is cryptographically bound to a specific WIT; tokens cannot be mixed or swapped
✓
aud pinning — WPT embeds the exact target URI; a stolen WPT is useless against any other endpoint
✓
mTLS transport — Certificate mismatch blocks unauthorized connections even before token inspection
Transaction Token attack surface
✓
Forged transaction context — attacker tries to mint a txntoken claiming sub=alice@corp.example. Defense: only the TTS private key can produce a valid ES256 signature; downstream services verify against TTS JWKS — forgery requires the TTS signing key.
⚠
Token reuse across transactions — a captured txntoken+jwt replayed in a different call context. Partial mitigation: 5-min TTL limits the window; the unique txn ID enables audit correlation. Note: the spec does not mandate per-token jti replay prevention by default — downstream services should log and monitor txn IDs.
✓
Context stripping — attacker removes the txntoken header before forwarding, eliminating user context. Defense: downstream services should enforce the txntoken as a required policy input and reject requests that omit it.
✓
azd tampering — attacker modifies authorisation details (e.g. increases payment amount) after minting. Defense: any modification to the payload invalidates the ES256 signature — detected immediately on validation.
SPIFFE Client Auth attack surface
⚠
WIT interception → Bearer escalation — stolen WIT used as client_assertion to obtain a Bearer token from the AS. Unlike WPT (which requires proof-of-possession), the OAuth assertion flow does not cryptographically bind the asserter to the WIT's cnf.jwk. Mitigation: mTLS in transit prevents interception; short WIT TTL (minutes) limits exploitation window; aud-restricted WITs reduce scope.
✓
Expired WIT replay — attacker stores and later reuses an old WIT as client_assertion. Defense: AS validates the exp claim; expired WITs are rejected. WIT short-TTL makes the window small.
✓
Issuer confusion / key substitution — attacker presents a JWT with the right iss value but signed with a different key. Defense: AS validates the ES256 signature against the IdP's live JWKS; forgery requires the IdP's EC P-256 private key.
✓
Scope escalation — workload requests wider OAuth scopes than its SPIFFE ID is entitled to. Defense: AS enforces allowed-scope policy keyed on sub = SPIFFE URI; the SPIFFE identity is unforgeable (signature-backed), making it a strong policy anchor.
Defence complete. From raw TCP with no identity, to cryptographically verifiable, short-lived, replay-protected, fine-grained workload identity. One more chapter shows how OpenID Federation lets entirely separate organisations trust each other's IdPs — without a shared secrets or a pre-arranged static key list.
Chapter 10 of 12
OpenID Federation
Two organisations have their own IdPs and never exchanged a single key out-of-band. OpenID Federation 1.0 lets a shared Trust Anchor publish cryptographically-signed Subordinate Statements that vouch for each IdP, so any relying party can verify the chain at runtime.
Generate a Trust Anchor key pair, an IdP-B key pair, build a self-signed Entity Configuration for IdP-B, and have the Trust Anchor sign a Subordinate Statement certifying IdP-B's public key.
Trust Anchor
—
IdP-B Entity
—
Authority Hint (Trust Anchor)
—
Entity Configuration JWT (IdP-B self-signed)
Step 2 — Verify the trust chain
The Resolver walks the chain: parse the EC to find authority_hints, fetch the Subordinate Statement from the Trust Anchor, verify the SS signature, extract IdP-B's key from the SS, verify the EC was signed by that key.
Extract IdP-B public key from certified JWKS in SS
5
Verify EC self-signature with the key extracted from SS
6
Cache resolved entity — min(EC.exp, SS.exp)
Resolved key (X coord)
—
Certified Entity
—
No pre-shared keys needed. The resolver derived IdP-B's public key entirely from the signed chain — Trust Anchor → Subordinate Statement → Entity Configuration.
Step 3 — Federated exchange
A workload presents a WIT signed by IdP-B. The relying party has no static entry for IdP-B — it resolves the key dynamically via the federation resolver, then validates the WIT normally.
WIT issued by IdP-B (dynamically trusted)
Dynamic trust in action: The validator held zero static knowledge of IdP-B before this request. Trust was established entirely through the OID-FED chain anchored at trust-anchor.corp.example.
Story complete. From raw TCP with no identity — to cryptographically verifiable, short-lived, replay-protected, fine-grained, federated workload identity across independent trust domains. All cryptographic operations ran in real Go code compiled to WebAssembly in your browser.
Chapter 11 of 12
Transaction Tokens
A WIT answers "who is this workload?" — but in a microservice call chain, the receiving service also needs to know "why is this call happening?"Transaction Tokens (txntoken+jwt, draft-ietf-oauth-transaction-tokens-11) capture the full originating context — user identity, authorisation details, and transaction ID — so every hop in a chain can trust the same root decision without re-authenticating the user.
Spec: draft-ietf-oauth-transaction-tokens-11 | typ: txntoken+jwt | immutable across call chain
1
Gateway receives Alice's request → calls TTS to mint a txntoken+jwt encoding Alice's identity, originating IP, gateway SPIFFE ID, and AS-granted authorisation details.
2
TTS returns signed txntoken+jwt (ES256, 5-min TTL). Gateway stores it and forwards the downstream call to Workload B with its own WIT+WPT plus the txntoken in a separate header.
3
Workload B validates the WIT+WPT (service authentication) then validates the txntoken (user/transaction context). It now knows who called it and why — from a single verified root.
4
B calls C, forwarding the same txntoken unmodified. C verifies it with the TTS public key. Full auditability across the chain — one mint, many hops.
The problem
Workload A authenticates Alice and calls B → C. B and C see only the service WIT — they have no idea Alice triggered this. Per-hop user identity is error-prone and inconsistent.
The Txn-Token solution
At the entry point a Transaction Token Service (TTS) mints a txntoken+jwt that is immutable and forwarded unchanged through the chain. Every service verifies the same token.
①User request arrives at the edge workload (gateway). The TTS mints a txntoken+jwt carrying the user's identity, originating IP, entry-point workload SPIFFE ID, and the AS-granted authorisation details.
②Gateway forwards the request to Workload B with its WIT+WPT (service auth) plus the Txn-Token in a separate header. The Txn-Token is not replaced — it travels unmodified.
③Workload B validates the WIT+WPT for service identity, then validates the Txn-Token for user context. It now knows both who the caller workload is and who the end user is and what they were authorised to do.
④B calls C in the same way. C sees the same Txn-Token — full auditability across every hop, signed once by the TTS, verifiable by any service with the TTS public key.
WPT binding: A WPT can optionally include a tth claim — base64url(SHA-256(txntoken_compact)) — cryptographically tying the per-request proof to the transaction. The pkg/wpt generator accepts a TxnToken field that sets this automatically.
10 tests in pkg/txntoken/ — happy path, typ header, txn ID propagation, authorisation details, wrong key, expired token, issuer mismatch, missing subject/audience, hash determinism, empty string guard.
Chapter 12 of 12
SPIFFE Client Authentication
Workloads already carry WITs that cryptographically prove their SPIFFE identity. Traditional OAuth client_credentials flows require a client_secret — a credential that must be stored, rotated, and protected. SPIFFE Client Auth (draft-ietf-oauth-spiffe-client-auth-02) eliminates the secret entirely: the WIT itself becomes the client assertion.
IdP/SPIRE issues a wit+jwt to the workload. The WIT's cnf.jwk embeds the workload's public key. No client_secret is generated or stored anywhere.
2
Workload sends POST /token with grant_type=client_credentials, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, client_assertion=<WIT>.
3
OAuth AS fetches the IdP's JWKS, verifies the WIT's ES256 signature and exp. On success it issues an opaque Bearer access token with sub=spiffe://…/svc/billing.
4
Workload calls the Resource API with Authorization: Bearer <token>. The API sees sub = SPIFFE URI — full workload identity. Zero secrets in the entire flow.
Standard foundation (RFC 7523)
RFC 7523 defines urn:ietf:params:oauth:client-assertion-type:jwt-bearer — an OAuth client can authenticate with a signed JWT instead of a secret. SPIFFE Client Auth profiles this for WIMSE workloads: the WIT is that signed JWT.
What the AS gains
The AS validates the WIT's ECDSA signature against the IdP's JWKS — trust it already extends for workload auth. No client_id/client_secret pre-registration. The SPIFFE URI in sub serves as the client identifier.
①Workload holds its WIT (issued by IdP). It needs an OAuth Bearer token to call a Resource API — but there is no client_secret anywhere.
②Workload sends POST /token to the OAuth AS with grant_type=client_credentials, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, and client_assertion=<WIT>.
③AS fetches the IdP's JWKS, verifies the WIT's ES256 signature and expiry. On success it issues a Bearer access token with sub=spiffe://…/svc/billing — the workload's SPIFFE identity is preserved.
④Workload calls the Resource API with the Bearer token. The resource server sees sub = SPIFFE URI. Zero secrets exchanged anywhere in the entire flow.
Credential lifecycle solved: WITs have short TTLs — when one expires the workload requests a fresh one from its IdP automatically. No operator involvement, no rotation scripts, no secret scanners, no vault integrations. The AS needs only the IdP's public key, already distributed via /.well-known/jwks.json.
Story complete. From raw TCP with no identity — to cryptographically verifiable, short-lived, replay-protected, fine-grained, federated workload identity with transaction context and secretless OAuth authentication. All cryptographic operations ran in real Go code compiled to WebAssembly in your browser.