Standards: WIT -02 WPT -01 Txn-Token -11 SPIFFE-CA -02 Identifiers -03 mTLS -02 Arch -08 DPoP RFC 9449 OID-FED 1.0 Final Standards reference →
Chapter 1 of 12

The Problem

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?

Workload A cloud-a.example Attacker intercepts! Workload B cloud-b.example ? No identity proof
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.

Spec: draft-ietf-wimse-workload-creds-02  |  typ: wit+jwt  |  alg: ES256
Identity Provider idp.cloud-a.example Workload A cloud-a.example Workload B cloud-b.example
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.

Spec: draft-ietf-wimse-mutual-tls-02  |  EC P-256, TLS 1.3+, URI SAN
Unauthenticated
TLS Handshake
mTLS Established
Workload A spiffe://cloud-a.../a Workload B spiffe://cloud-b.../b
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.

Spec: draft-ietf-wimse-wpt-01  |  typ: application/wpt+jwt  |  TTL: 5 minutes
Workload A WIT + priv key Workload B verifies WIT+WPT
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.

Workload A sends WIT+WPT Validator 9 checks… WIT + WPT
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.

query allow/deny Workload A authenticated Middleware WIT validated Resource api:billing-data Policy Engine ABAC rules
Resource: Action:
Subject PatternResourceActionDecisionReason
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.

Format: JWT _sd hash array + Base64URL disclosures after ~ separator
Workload A presents SD-WIT Workload B sees only revealed SD-WIT
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.

Spec: draft-ietf-wimse-arch-08 — token exchange at trust-domain boundaries
Workload A cloud-a.example has WIT-A Token Exchange trusts both IdPs policy gateway Workload B cloud-b.example accepts WIT-B ① WIT-A ③ WIT-B ② Exchange validates WIT-A, applies trust policy, issues WIT-B signed by cloud-b IdP key IdP-A cloud-a.example IdP-B cloud-b.example
①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.
Emerging standard — AuthZEN (monitoring): RFC 8693 Token Exchange says the AS decision is "governed by local policy" — without defining how. draft-gazitt-oauth-authzen-token-exchange fills that gap: it specifies how the token exchange request (subject workload, target audience, grant type) maps onto an AuthZEN PDP API call — an interoperable interface for externalising the "which workload may exchange a WIT for a WPT targeting which audience" decision to any compliant policy engine (OPA, OpenFGA, Cerbos). Both drafts are rev 00 (individual, early stage). Tracked in standards-baseline.json.
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.

Workload A legitimate caller Attacker captured WPT Workload B records jti Workload B jti already seen! WIT+WPT WPT (replay)
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.

Spec: OpenID Federation 1.0 — entity statements, trust chains, trust anchors
Trust Anchor trust-anchor.corp.example signs Subordinate Statements SS (signed by anchor) SS (signed by anchor) IdP-A idp.cloud-a.example Entity Configuration JWT IdP-B idp.cloud-b.example Entity Configuration JWT Federation Resolver verifies trust chains at runtime SS JWT Key Resolved

Step 1 — Build the federation chain

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.

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.

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.

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
Gateway entry point Alice's request TTS mints txntoken 5-min TTL Workload B validates WIT+WPT + txntoken Workload C same txntoken unmodified Click Animate to see the flow mint txntoken WIT+WPT+Txn TxnToken
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.

Go — TTS issues & service validates

// pkg/txntoken — TTS mints
tts := txntoken.NewIssuer(
  "https://tts.cloud-a.example",
  ttsKey, 5*time.Minute)

tok, _ := tts.Issue(txntoken.IssueOptions{
  Subject:   "alice@corp.example",
  Audiences: []string{"https://api.…"},
  ReqCtx:    &txntoken.RequestContext{
    ReqIP: "203.0.113.42",
    ReqWL: "spiffe://…/svc/gateway",
  },
})

// Downstream service validates
v := txntoken.NewValidator(
  "https://tts.cloud-a.example", ttsPub)
claims, _ := v.Validate(tok)
// claims.Txn, claims.ReqCtx, claims.AzDetails
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.

Spec: draft-ietf-oauth-spiffe-client-auth-02  |  RFC 7523 JWT Bearer client assertion  |  client_assertion_type: jwt-bearer
IdP / SPIRE issues wit+jwt cnf.jwk bound Workload spiffe://…/billing no client_secret OAuth AS validates WIT sig issues Bearer Resource API Bearer token sub = SPIFFE ID Click Animate to see the flow wit+jwt client_assertion Bearer API call
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.

Go — AS-side authenticator

// pkg/spiffeclientauth
auth := spiffeclientauth.NewAuthenticator(
  "https://as.cloud-a.example",
  wit.NewValidator(
    "https://idp.cloud-a.example",
    idpPub,
  ),
  time.Hour,
)

tok, err := auth.Authenticate(
  spiffeclientauth.AuthRequest{
    ClientAssertion:     witToken,
    ClientAssertionType: spiffeclientauth.ClientAssertionType,
    Scope: "read:orders",
  },
)
// tok.Sub   = "spiffe://cloud-a.example/svc/billing"
// tok.Token = opaque Bearer string
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.
8 tests in pkg/spiffeclientauth/ — happy path, token uniqueness per call, wrong assertion type, empty assertion, tampered WIT, expired WIT, issuer mismatch, scope passthrough.
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.
Chapter 13 of 13

HTTP Message Signatures: Sign the Request

Instead of attaching a separate proof JWT (the WPT approach), the HTTP request itself is signed. The WIT rides in the Workload-Identity-Token header. The signature covers the method, path, query, and the WIT header — binding proof, identity, and request in one atomic operation.

Spec: draft-ietf-wimse-http-signature-07  |  RFC 9421 HTTP Message Signatures
Dimension WPT (JWT proof) HTTP-Sig (this chapter)
Proof format Separate JWT (Authorization: WPT …) RFC 9421 HTTP Signature (three headers)
Wire headers Workload-Identity-Token + Authorization: WPT Workload-Identity-Token + Signature-Input + Signature
Body coverage Via Content-Digest hash in WPT (optional) Via content-digest covered component when header present
Replay protection jti claim tracked server-side nonce + expires in Signature-Input params
Workload A cloud-a.example ECDSA P-256 idle HTTP request headers Workload-Identity-Token: <WIT> Signature-Input: sig1=(…);nonce=… Signature: sig1=:<base64>: tag="wimse-workload-to-workload" Service B cloud-b.example verifies sig waiting… @authority excluded — TLS-proxy-tolerant (§4.1)

Live Demo

No separate proof JWT: the ECDSA signature lives in standard HTTP headers. Proxies and gateways that strip unknown JWT headers leave the signature intact. The wimse-aud parameter in Signature-Input prevents signature replay across different service endpoints.
11 tests in pkg/httpsig/ — HappyPath, AudienceMismatch, TamperedSignature, ExpiredSignature, ReplayProtection, WrongKey, MissingRequiredComponent, ContentDigestCoveredWhenPresent, InputValidation_NoWIT, InputValidation_NoAudience, MissingWITHeader — all passing.