Large enterprises running workloads across AWS, Azure, and GCP accumulate siloed, duplicate identities — one per cloud, per service, per team. The result: unauditable access, orphaned credentials, and a constant risk of lateral movement.
| Problem | How it manifests | Business risk |
|---|---|---|
| Duplicate credentials | billing-svc has 3 IAM roles / service principals / SAs | 3× rotation surface, 3× audit burden |
| Orphaned identities | Decommissioned services leave IAM roles that are never cleaned up | Lateral movement vector; compliance failure |
| Policy drift | AWS policy updated; Azure policy forgotten; GCP policy conflicts | Unintended access granted or blocked |
| Cross-cloud access | billing-svc (AWS) calls payment-svc (GCP) via long-lived secrets | Secret leakage = full compromise |
| Audit gaps | No unified identity → no unified access log across clouds | SOC 2 / ISO 27001 non-compliance |
Every team asks this first. Here's what each existing approach provides — and the specific gap that WIMSE fills.
| Existing approach | What it gives you | What it doesn't solve |
|---|---|---|
| OAuth Bearer token Authorization: Bearer eyJ… |
Authorization for an action or scope on one AS | Ambient authority — anyone holding the token can use it (no key binding). Stolen token = full access until expiry. Cross-cloud calls require federation setup per AS pair. No per-request, per-URI proof. |
| mTLS alone (SPIFFE/SPIRE) X.509 cert, URI SAN |
Strong transport-layer identity per TCP connection | Certificate identity doesn't survive TLS-terminating load balancers or service meshes. Cert validity is hours or days — no short-lived, per-request proof. Doesn't express what the workload is calling or why. |
| API keys / shared secrets X-Api-Key: sk-live-XXXX |
Simple, fast service-to-service auth | Static and long-lived. Shared across all instances of the service — one compromise = full compromise. No automatic expiry. No workload-specific identity; the key is indistinguishable from a leaked copy. |
| Cloud IAM roles AWS IRSA · GCP WIF · Azure MI |
Strong cryptographic identity within one cloud | Cloud-specific — AWS IRSA tokens aren't accepted by a GCP resource. No portable identity URI that works cross-cloud, on-prem, or at edge. No standard for cross-domain workload auth. |
| Kubernetes Service Accounts projected serviceAccountToken |
Pod identity within a single cluster | Cluster-scoped. All pods in the same Deployment share one SA — no per-workload-instance identity. Tokens aren't accepted outside the cluster they were issued for. |
spiffe://trust-domain/svc/billing URI that works everywhere, a short-lived WIT bound to the workload's EC key pair (stolen token → useless without the key), and a per-request WPT tied to the exact target URI — making replay and forgery cryptographically impossible. Cloud IAM still handles infrastructure bootstrapping; WIMSE handles every application-layer call.
WIMSE (Workload Identity in Multi-System Environments) + SPIFFE (Secure Production Identity Framework for Everyone) provides a portable, cryptographic identity layer that sits above cloud-native identity — giving every workload a single canonical identity that works everywhere.
spiffe://corp.example/svc/billing — portable across ALL clouds. WIT + WPT prove identity per request. OID-FED federates trust domains.
spiffe://corp.example/svc/billing — the same identity string in AWS, Azure, GCP, on-prem, and edge.
WITs expire in minutes/hours. No long-lived secrets to rotate or leak.
WPTs bind each API call to a specific target URI and the caller's private key — replay and forgery are cryptographically impossible.
OID-FED lets independently-operated IdPs (Okta, Keycloak, Vault, Entra ID) join a common trust fabric without sharing keys.
| Layer | Who owns it |
|---|---|
| Compute (EC2/Pod/VM) | Cloud provider |
| Bootstrap identity (IRSA/WIF/WIP) | Cloud provider |
| Workload key generation | SPIFFE/SPIRE or Vault |
| WIT issuance | WIMSE IdP (Okta/Keycloak/Vault) |
| API-level auth (WIT+WPT) | WIMSE protocol |
| Cross-cloud trust | OID-FED trust anchor |
Most enterprises already have one (or more) of these identity platforms running. WIMSE can be layered on top of each — you keep your existing investment and add portable workload identity on top.
spiffe_uri and workload_key claims.typ: wit+jwt) with correct cnf.jwk.sub=spiffe://corp.example/....cloud-a.example, cloud-b.example).cnf.jwk (workload public key) to the token. This transforms a standard Keycloak JWT into a valid WIT.cnf.jwk.OID-FED 1.0 is the trust governance layer that links independently-operated IdPs into a coherent enterprise trust fabric — without requiring point-to-point key exchange between every pair of IdPs.
spiffe://corp.example/svc/billing) regardless of which cloud it runs on or which IdP issued its token. The Trust Anchor is the single source of truth — not any individual IdP.
The gateway peeks at the iss claim (e.g., idp.azure.corp.example) without verifying the signature yet.
No static key for this issuer? The resolver fetches the Entity Configuration from https://idp.azure.corp.example/.well-known/openid-federation.
EC's authority_hints points to the corporate Trust Anchor. The resolver fetches the Subordinate Statement and verifies it with the pre-configured anchor key.
The issuer's public key (certified by the SS) is used to verify the WIT signature. If valid, the workload is authenticated.
Resolved keys are cached until min(EC.exp, SS.exp). Subsequent requests use the cache; no network round trip needed.
Workload-Identity-Token header, peeks at iss claim: idp.azure.corp.example — no cached key for this issuer.https://idp.azure.corp.example/.well-known/openid-federation. Entity Configuration JWT contains the IdP's JWKS and authority_hints.https://trust-anchor.corp.example/federation/fetch?sub=idp.azure.corp.example. Subordinate Statement certifies the IdP's public key, signed by the anchor.min(EC.exp, SS.exp).sub=spiffe://corp.example/svc/billing authenticated. Request forwarded.There is no universally correct answer. The right model depends on your organisation's size, regulatory requirements, operational maturity, and tolerance for single points of failure.
Workload identity and secrets management are closely related but distinct concerns. WIMSE handles identity (who is this workload?) while secrets management handles access to sensitive data (what secrets may this workload use?).
EC P-256 key pair generated at startup. Private key in memory only. Public key in WIT cnf.jwk. WIMSE handles this.
DB credentials, API keys, TLS certs — retrieved from Vault at startup using the WIMSE WIT as the Vault auth credential. Vault issues dynamic secrets.
Short-lived X.509 certs with SPIFFE URI SANs issued by Vault PKI. Renewed every 1–8 hours by SPIRE or Vault Agent. Tied to the workload identity key.
Cloud-native credential (IRSA/WIF/GKE WIP) → WIMSE IdP → WIT. No manually managed secrets needed for this step.
Present WIT to Vault's JWT auth endpoint. Vault validates WIT via the WIMSE IdP's JWKS. Returns a Vault token scoped to the workload's policy.
Use the Vault token to fetch dynamic DB credentials, encrypt API keys, or generate mTLS certs from Vault PKI. All accesses are in Vault's audit log.
Use WIMSE WIT + per-request WPT for all inter-service communication. No long-lived shared secrets involved.
Moving from cloud-native IAM silos to a WIMSE-based identity fabric is a phased journey. Each phase delivers immediate value and reduces risk, making the next phase easier.
Assign spiffe://corp.example/svc/<name> URIs to all workloads. No code changes yet — this is purely a naming exercise. Build the inventory that will drive policy later.
Deploy a central WIMSE IdP (Keycloak or Vault) in a pilot cloud. Workloads in the pilot exchange cloud-native credentials for WITs. mTLS certs from Vault PKI for transport security.
Instrument the 5–10 highest-value service pairs with WIMSE middleware. This immediately eliminates long-lived shared secrets on the busiest call paths and provides per-request audit.
Establish the corporate Trust Anchor. Register existing IdPs (Entra ID, Okta, Keycloak) as subordinates. Cross-cloud service calls can now resolve trust dynamically — no more bilateral key exchange.
Deploy an authorization layer (OpenFGA or OPA) behind the WIMSE gateway. Replace overly broad IAM policies with subject × resource × action rules driven by SPIFFE URI patterns.
As AI agents and service orchestrators proliferate, WIMSE's SPIFFE URI namespace and short-lived cryptographic tokens apply directly: each agent process gets a workload identity, and every action it takes can be attributed to that identity in the audit log. No new protocol needed — WIMSE scales naturally to agentic patterns.
| Metric | Before WIMSE | After WIMSE |
|---|---|---|
| Credential types per workload | 3–5 (IAM role, SA, secret, cert, token) | 1 (EC P-256 key pair) |
| Longest-lived credential | Often permanent (IAM role, static API key) | WIT: 1h max; WPT: 5 min |
| Cross-cloud auth mechanism | Long-lived shared secret or OIDC trust per pair | OID-FED — dynamic, anchor-governed |
| Identity audit trail | Per-cloud, not unified | Single SPIFFE URI in every access log |
| Time to add new trusted IdP | Days (bilateral key exchange) | Minutes (Trust Anchor + SS) |
| M&A identity integration | Weeks of IAM configuration | Register new IdP with Trust Anchor |
WIMSE currently relies on EC P-256 (ECDSA) for all token signatures and mTLS certificates. Shor's algorithm running on a cryptographically relevant quantum computer (CRQC) can break elliptic curve discrete logarithm in polynomial time — rendering every WIT, WPT, and mTLS cert forgeable.
EC P-256 relies on the Elliptic Curve Discrete Logarithm Problem: given public point Q = k·G, finding private key k requires ~2128 classical operations — computationally infeasible. Shor's algorithm on a CRQC solves ECDLP in O(n³), extracting k in seconds.
CRYSTALS-Dilithium. Lattice-based. Primary NIST recommendation for digital signatures.
| Public key | 1312 B (vs 64 B for EC) |
| Signature | 2420 B (vs 64 B for EC) |
| Security | 128-bit PQ equivalent |
| WIMSE use | WIT signing, WPT signing |
CRYSTALS-Kyber. Lattice-based key encapsulation. Replaces ECDH in TLS 1.3 key exchange.
| Public key | 1184 B (vs 32 B for ECDH) |
| Ciphertext | 1088 B (vs 32 B) |
| Security | 128-bit PQ equivalent |
| WIMSE use | mTLS key exchange in TLS 1.3 |
SPHINCS+. Stateless hash-based signatures. Conservative option; much larger signatures but only relies on hash security.
| Public key | 32 B (small!) |
| Signature | 8 KB – 50 KB |
| Security | 256-bit PQ equivalent |
| WIMSE use | Trust Anchor signing (long-lived keys) |
Click each era to explore the token structure, pros, cons, and required migration actions for that phase of the quantum transition.
{
"typ": "wit+jwt",
"alg": "ES256"
}.{
"sub": "spiffe://cloud-a/svc/billing",
"cnf": { "jwk": { "kty":"EC",
"crv":"P-256","alg":"ES256" } },
"exp": ..., "jti": "..."
}
TLS 1.3 ECDHE + ECDSA EC P-256 cert URI SAN: spiffe://...
| WIMSE Component | Current (EC P-256) | PQ-Safe | Migration Path |
|---|---|---|---|
| WIT signing | ES256 (ECDSA) | ML-DSA (alg=ML-DSA-44) | Hybrid: both alg in header during transition |
| WPT signing | ES256 (ECDSA) | ML-DSA or SLH-DSA | Same hybrid approach; WPT key confirmed in WIT cnf.jwk |
| mTLS certificates | EC P-256 X.509 | ML-DSA + ML-KEM in TLS 1.3 | Dual-cert: server presents both EC and PQ cert |
| cnf.jwk key type | EC {kty:"EC", crv:"P-256"} | OKP {kty:"OKP", crv:"ML-DSA-44"} | New JWK key type. WIT parser must handle both. |
| OID-FED entity config | EC key in JWKS | ML-DSA key in JWKS | Publish both EC and ML-DSA keys during transition |
| Trust Anchor signing | EC P-256 | SLH-DSA (conservative, high-assurance) | Trust Anchors have long-lived keys → prioritise PQ-migration |
During the EC → PQ transition, WIMSE tokens carry both an EC and a PQ signature. Validators that understand PQ verify the PQ signature; legacy validators fall back to EC. Once PQ is universal, the EC signature is dropped.
WIT header: alg:["ES256","ML-DSA-44"]. Token carries both signatures. Works with EC-only and PQ-capable validators.
All validators updated to accept ML-DSA. Issuers prefer ML-DSA but still include EC sig. JWKS exposes both key types.
EC-only tokens rejected. alg:ML-DSA-44 mandatory. mTLS requires ML-KEM. Trust Anchor uses SLH-DSA. Migration complete.
| Threat | Vector | Classical Risk | Quantum Risk | Mitigation |
|---|---|---|---|---|
| T-Q1: CRQC Key Extraction | Shor's algorithm extracts EC P-256 private key from public point Q | Low — 2¹²⁸ ops | Critical — seconds on CRQC | Migrate all WIT/WPT keys to ML-DSA-44; deprecate ES256 |
| T-Q2: HNDL — WIT/WPT Tokens | Collect WITs/WPTs in transit today; recover sig keys post-CRQC; forge workload identity | None | High — every workload identity forgeable | Deploy hybrid signing from 2028; PQ-only mandatory by 2032 |
| T-Q3: HNDL — mTLS Sessions | Record TLS handshakes; ECDH session secrets recoverable via Shor's once CRQC available | None | High — all past mTLS session keys exposed | Deploy ML-KEM-768 (X25519MLKEM768) for TLS key exchange |
| T-Q4: Trust Anchor Forgery | Extract Trust Anchor EC key; issue fake OID-FED entity statements; take over federation | Low | Critical — entire federation trust graph collapses | SLH-DSA for Trust Anchors (highest priority — long-lived keys) |
| T-Q5: JTI Hash Weakening (Grover) | Grover's algorithm halves SHA-256 pre-image resistance from 256 to 128 effective bits | Negligible | Medium — SHA-256 JTIs weaker but not immediately broken | Upgrade JTI generation to SHA-512 or 256-bit cryptographic random |
| T-Q6: WPT Proof Token Forgery | Recover workload private key via Shor's; generate valid WPT for any target URI | Low | Critical — per-request proof protection bypassed for all historical WPTs | Migrate WPT cnf.jwk to OKP/ML-DSA; enforce short proof TTL ≤ 5 min |
cnf.jwk), and validation (middleware), each component can be upgraded independently. The SPIFFE URI identity (sub) remains unchanged — only the cryptographic algorithm changes.
draft-ietf-jose-pqc) for finalised JOSE PQ algorithm identifiers.
A high-availability WIMSE deployment runs multiple IdP replicas. Without consensus, two replicas might issue conflicting tokens, or a JTI replay might succeed by hitting a replica that hasn't seen the first request. Paxos provides the agreement primitive that makes distributed WIMSE correct.
| Problem | Without consensus | With Paxos |
|---|---|---|
| Token issuance in HA cluster | Two primaries race → duplicate JTIs, split claims | Only one leader issues; majority agrees before commit |
| JTI replay detection | Replay succeeds by hitting a replica that hasn't seen the JTI | JTI store is replicated via Raft/Paxos; replay rejected cluster-wide |
| Trust Anchor key rotation | Replicas briefly serve old and new keys — validation fails | Key rotation committed atomically; all replicas switch simultaneously |
| Token revocation propagation | Revocation reaches some replicas later — revoked tokens still accepted | Revocation log replicated; all replicas reject revoked token immediately |
| OID-FED SS cache invalidation | Stale Subordinate Statements used after expiry | Cache expiry events propagated consistently to all gateway nodes |
Proposal value = {sub, cnf.jwk, jti, exp}. The proposer (IdP primary) must obtain a quorum (2 of 3) before issuing the token.
Most WIMSE deployments use Raft (Multi-Paxos equivalent with simpler leader election) rather than classic Paxos. etcd and CockroachDB both use Raft. A WIMSE HA cluster with Raft looks like:
Odd quorum. Leader elected by Raft. Leader handles all token issuance writes. Followers serve reads (JWKS, health).
Every JTI is written to the Raft log before the token is issued. All replicas see the same JTI history → replay detection works across nodes.
New signing key committed as a Raft entry with a future activation timestamp. All replicas switch at the same logical time → no split-brain key state.
Revoked JTIs written as Raft entries. Gateways subscribe to the revocation feed via SSE or gRPC stream from the leader.
This PoC is built on nine implemented IETF and OpenID drafts, plus five additional drafts under active monitoring (see table below). Every implemented standard has an animated demo chapter. A GitHub Actions workflow checks the IETF Datatracker API daily and opens a GitHub issue whenever a new revision is published.
last_known_rev tracks the latest published revision; implemented_rev tracks what this PoC has actually implemented.
Last checked: 2026-08-02 · standards-baseline.json · standards-tracker.yml
| Standard | Working Group | IETF Status | PoC Status | Rev | Affected files | Notes |
|---|---|---|---|---|---|---|
| WIT — Workload Identity Credentials | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -02 |
pkg/wit, internal/idp, internal/workload |
WIT claims (typ=wit+jwt), cnf.jwk with alg=ES256, SPIFFE URI sub |
| WPT — Workload Proof Token | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -01 |
pkg/wpt, internal/workload |
typ=application/wpt+jwt, aud=request URI, wth=SHA-256(WIT), tth=SHA-256(Txn-Token), jti replay protection |
| Txn-Token — OAuth 2.0 Transaction Tokens | IETF OAuth WG | ● WG Draft | ✓ Implemented | -11 |
pkg/txntoken, pkg/wpt (tth) |
typ=txntoken+jwt, propagates user identity + authz context; WPT tth binds hop proofs to the transaction |
| Identifiers — Workload Identifier | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -03 |
pkg/wit, pkg/wpt, internal/idp |
SPIFFE URI format (spiffe://trust-domain/path), no query / fragment / port allowed |
| mTLS — Mutual TLS Binding | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -02 |
pkg/keys/mtls.go, internal/workload |
X.509 URI SAN format, EC P-256 requirement, TLS 1.3 minimum |
| Arch — WIMSE Architecture | IETF WIMSE WG | ● WG Draft | ✓ Implemented | -08 |
internal/exchange |
Token exchange patterns at trust-domain boundaries, HTTP header names (Workload-Identity-Token) |
| OID-FED — OpenID Federation 1.0 | OpenID Foundation | ● Published (OIDF) | ✓ Implemented | 1.0-41 |
pkg/federation, internal/exchange |
Entity Configuration JWT, Subordinate Statement, authority_hints, trust chain resolution, min(EC.exp,SS.exp) caching |
| SPIFFE Client Auth — JWT-bearer OAuth assertion | IETF OAuth WG | ● WG Draft | ✓ Implemented | -02 |
pkg/spiffeclientauth |
WIT as client_assertion (urn:…:jwt-bearer); AS validates WIT sig, returns Bearer token with sub=SPIFFE ID |
| Identity Chaining — JWT Authorization Grant | IETF OAuth WG | ● WG Draft | ✓ Implemented | -17 |
pkg/identitychaining |
typ=jwt-authz-grant; cross-trust-domain SPIFFE ID propagation; aud=target AS, jti replay, 5-min TTL |
| JWT-BCP — JWT Best Current Practices | IETF OAuth WG | ● Active BCP | ✓ Implemented | bis |
All validator files (pkg/wpt, pkg/sdwit, pkg/txntoken, pkg/identitychaining) |
BCP compliance audit 2026-08-02: WithValidMethods(ES256), WithExpirationRequired, WithIssuedAt, typ check — mitigates alg confusion, alg:none, missing-exp. Commit: e6ff6a2 |
| HTTP-Sig — WIMSE HTTP Signature Auth | IETF WIMSE WG | ● WG Draft | ◌ Monitoring | — |
pkg/wpt (future — if WG adopts) |
RFC 9421 HTTP Message Signatures as alternative to WPT; would require pkg/httpsig if adopted as primary by the WG |
| CrossOrgDelegation — Cross-Org Workload Delegation | IETF WIMSE WG | ◌ Individual draft | ◌ Monitoring | — |
pkg/identitychaining, internal/exchange |
Formalises cross-org delegation requirements; informs Phase 4 token exchange policy. No breaking change to current implementation. |
| SD-JWT VC — Selective Disclosure VC | IETF OAuth WG | ● WG Draft | ✗ Excluded | -17 |
N/A — pkg/sdwit uses base SD-JWT |
VC layer (vct, status lists, Issuer-Holder-Verifier) designed for human credentials. M2M selective disclosure already handled by pkg/sdwit using base SD-JWT. VC layer adds no value for workload auth. |
| PropagationContext — Multi-Hop Identity Context | IETF OAuth WG | ◌ Individual draft | ◌ Monitoring | — |
pkg/txntoken (future extension) |
Structured identity propagation context for OAuth call chains; complementary to Txn-Token; tracked for Phase 4 multi-hop identity enrichment |
scripts/check_standards.py every morning. The script queries the IETF Datatracker REST API for each tracked draft and compares the returned rev field against standards-baseline.json.last_known_rev is updated and committed with [skip ci] to avoid triggering further runs. A developer sets implemented_rev manually after updating the Go code to match.standards-update labelled issue is opened listing the new revision, the PoC impact note, affected files, and an action checklist: read the IETF diff, check for breaking claim or header changes, update the implementation, and bump implemented_rev.WIMSE enforces multiple independent validation layers. Each layer catches a distinct class of attack. This scenario walks through six real attack vectors an adversary might attempt against a WIMSE-protected service — and shows exactly which check stops them.
| # | Check | Spec clause | Catches |
|---|---|---|---|
| 1 | typ = wit+jwt | workload-creds §4.1 | Tokens of wrong type (e.g. a WPT presented as a WIT) |
| 2 | ECDSA signature valid against IdP public key | RFC 7515 §5.2 | Tampered payload or forged tokens |
| 3 | iss matches expected issuer | RFC 7519 §4.1.1 | Tokens from an untrusted or attacker-controlled IdP |
| 4 | exp not in the past | RFC 7519 §4.1.4 | Captured and replayed stale tokens |
| 5 | cnf.jwk parsed — workload public key extracted | workload-creds §4.2 | WITs with missing or malformed confirmation key |
| 6 | WPT typ = application/wpt+jwt | wpt §3.1 | Wrong proof token type |
| 7 | WPT signature valid against cnf.jwk from WIT | wpt §4 | Proof signed with a different key than claimed in WIT |
| 8 | WPT aud = request URI (scheme + host + path) | wpt §3.2 | Proof captured for endpoint A, replayed against endpoint B |
| 9 | wth = base64url(SHA-256(WIT wire bytes)) | wpt §3.3 | WIT and WPT issued by different parties (mix-and-match) |
| 10 | WPT jti not in replay store | wpt §4.2 + RFC 9449 | Exact replay of a previously accepted request |
sub to a higher-privilege identity).→ 401 Unauthorized — jwt: signature is invalidexp claim is in the past. Short-lived tokens (1 h default) limit the window an attacker can exploit a stolen credential.→ 401 Unauthorized — jwt: token is expiredidp.cloud-b.example and presents it to Workload B in cloud-a.example, hoping the validator won't check the issuer.→ 401 Unauthorized — issuer mismatchhttps://api.example/orders and replays it against https://api.example/payments (a higher-privilege endpoint).aud in the WPT is compared against the actual request URI. Mismatch → reject. Each WPT is single-use for one specific endpoint.→ 401 Unauthorized — aud mismatchjti UUID is stored on first acceptance. The second request presents the same jti, which is already in the replay store → rejected immediately.→ 401 Unauthorized — jti already seencnf.jwk in the WIT. Attacker's key ≠ victim's cnf.jwk → signature invalid.→ 401 Unauthorized — WPT: signature invalid| Defence layer | Without it | With it |
|---|---|---|
| Short-lived WIT (1 h TTL) | Stolen token valid indefinitely | Attack window capped at 1 hour |
Per-request WPT (aud binding) | One captured proof works on any endpoint | Each proof is endpoint-scoped and single-use |
WPT jti replay store | Passive attacker can replay valid requests | Exact replay rejected in O(1) via in-memory set |
wth binding (WIT ↔ WPT) | Attacker can mix-and-match WIT from A with WPT from B | WIT and WPT must form a cryptographically linked pair |
mTLS (cert URI SAN = WIT sub) | Token presented over plain TLS — MITM can forward stolen headers | Transport identity must match token identity — MITM impossible without the private key |
WIMSE answers who is this workload? It does not answer is this workload allowed to do this specific thing? Fine-grained authorization (FGA) is the complementary layer that closes that gap — and it is the layer that most enterprises deprioritise because it is hard to integrate. This scenario makes the case for it and shows how it composes naturally with WIMSE.
spiffe://cloud-a.example/svc/billing calling?"sub claim that downstream services can trust unconditionally.
billing-svc allowed to write to invoice:monthly-2026-07?"OpenFGA uses a type system and tuples (user, relation, object). The WIMSE sub becomes the FGA user. Policy changes happen in the store — no code deployment needed.
billing-svc write invoice:*audit-svc read invoice:*billing-svc delete invoice:*audit-svc write invoice:*ops-svc delete invoice:*
| The objection | The reality |
|---|---|
| "It adds an extra network hop per request" | OpenFGA, OPA, and Cedar all support local evaluation via a sidecar or embedded SDK. A policy decision is typically <1 ms. Caching with short TTLs (seconds) further reduces overhead without losing freshness. |
| "Every service needs to be integrated" | The FGA check lives in the gateway or a shared middleware — exactly where WIMSE validation already lives. Services receive the allow/deny verdict as a header or context value. No per-service code change needed. |
| "The schema is hard to keep current" | WIMSE's verified sub maps directly to an FGA user without translation. Relations and tuples change in the policy store — no code deployment. WIMSE makes FGA easier because identity is cryptographically verified, not asserted. |
| "We already have RBAC in our app" | Hardcoded RBAC in application code cannot express attribute- or relationship-based policies without deployments. It also cannot be audited or changed without a code review cycle — slowing down incident response by hours or days. |
| "We'll add it later" | The cost of retrofitting FGA grows with the number of services. Adding FGA early — when WIMSE is being deployed — means one integration point, not N. "Later" usually means never. |
sub → forwards X-Workload-Sub header to upstream → upstream calls OpenFGA /stores/{id}/check with user=sub, relation=action, object=resource. Policy tuples managed via GitOps — any team can propose a PR to grant or revoke access. No code deployment required.
Workloads already hold a WIT (wit+jwt) that proves their SPIFFE identity. Traditional OAuth client authentication requires a client_id and client_secret — a rotation burden that fails in ephemeral workload environments. SPIFFE Client Auth (draft-ietf-oauth-spiffe-client-auth-02) lets the workload use its existing WIT as a JWT bearer client assertion, authenticating to any OAuth 2.0 AS with zero pre-shared secrets.
No pre-shared client secret anywhere in the flow. The AS validates the WIT's ECDSA signature against the IdP's public key — the same trust it already extends to workload authentication.
POST /token)# application/x-www-form-urlencoded grant_type=client_credentials client_assertion_type=urn:ietf:params:oauth: client-assertion-type:jwt-bearer client_assertion=<compact WIT JWT> scope=read:orders write:payments # Response { "access_token": "<opaque 32-byte bearer>", "token_type": "Bearer", "expires_in": 3600, "sub": "spiffe://cloud-a.example/svc/billing" }
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
| Concern | client_secret approach | SPIFFE Client Auth |
|---|---|---|
| Secret storage | Secret must be stored in vault or env var; risk of leak or misconfiguration | No secret — the WIT is the credential; IdP's JWKS endpoint provides the verification key |
| Rotation | Manual rotation required; zero-downtime rollover is complex | WIT has short TTL; expiry is automatic rotation with no operator involvement |
| Identity in token | Access token carries only client_id — no workload identity | Access token's sub = SPIFFE URI — resource server gets workload identity for free |
| Per-workload credentials | One secret per registration — manual, audit-unfriendly, scale bottleneck | Every workload holds its own SVID; no registration needed at the AS |
8 tests in pkg/spiffeclientauth/spiffeclientauth_test.go: happy path, token uniqueness, wrong assertion type, empty assertion, tampered WIT, expired WIT, issuer mismatch, scope passthrough.
wit+jwt bound to the workload's public key via cnf.jwk. The workload holds this as its identity credential — no client_secret needed.client_assertionPOST /token with grant_type=client_credentials, client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer, and client_assertion=<WIT>.sub=spiffe://…/svc/billing.Authorization header. Resource server sees sub = SPIFFE URI — full workload identity, zero secrets exchanged anywhere in the flow.A workload in Cloud A needs to call an API in Cloud B. Cloud B's AS won't accept Cloud A's WIT — different trust domains, different signing keys. Identity Chaining (draft-ietf-oauth-identity-chaining-17) bridges this gap: Cloud A's AS issues a short-lived JWT Authorization Grant addressed to Cloud B's token endpoint. Cloud B validates it with only Cloud A's public key — no pre-configured secrets between the two AS instances.
Cloud B only needs Cloud A's public signing key — typically distributed via Cloud A's JWKS endpoint or a mutual trust agreement. The workload's SPIFFE ID flows through the grant's sub claim, giving Cloud B full auditability.
// Header { "alg": "ES256", "typ": "jwt-authz-grant" } // Payload { "iss": "https://as.cloud-a.example", "sub": "spiffe://cloud-a.example/svc/billing", "aud": ["https://as.cloud-b.example/token"], "iat": 1753649000, "exp": 1753649300, // 5-min TTL "jti": "kY2mNpXx…" // replay prevention }
pkg/identitychaining// Domain A: issue grant gi := identitychaining.NewGrantIssuer( "https://as.cloud-a.example", domainAKey, witValidator, 5*time.Minute, ) grant, _ := gi.Issue( witToken, "https://as.cloud-b.example/token", ) // Domain B: validate grant gv := identitychaining.NewGrantValidator( "https://as.cloud-a.example", domainAPub, ) claims, _ := gv.Validate( grant, "https://as.cloud-b.example/token", ) // claims.Subject = "spiffe://cloud-a.example/svc/billing"
| Concern | Without Identity Chaining | With Identity Chaining |
|---|---|---|
| Cross-domain auth | Workload can't authenticate to domain B — different trust roots, no shared secret | Domain A's AS issues a grant; domain B validates with domain A's public key only |
| Identity preservation | Workload must acquire domain B credentials with a separate static identity | SPIFFE ID from domain A preserved in grant's sub — full audit trail across boundaries |
| Infrastructure dependency | Requires federation agreement or a common token exchange service | Grant is self-contained — domain B needs no live connection to domain A after initial key exchange |
| Replay safety | A static cross-domain token could be replayed indefinitely | jti uniqueness + 5-minute TTL — intercepted grants expire quickly and cannot be replayed |
10 tests in pkg/identitychaining/identitychaining_test.go: happy path, typ header, audience mismatch, wrong signing key, expired grant, issuer mismatch, invalid WIT, missing inputs, end-to-end cross-domain flow.
wit+jwt to Cloud A's Authorization Server to prove its SPIFFE identity. AS-A validates signature and expiry.jwt-authz-grant with iss=as.cloud-a.example, sub=spiffe://cloud-a.example/svc/billing, aud=https://as.cloud-b.example/token, 5-min TTL, random jti.iss, aud, exp, and jti uniqueness using only Cloud A's public key — no pre-shared secrets.sub claim and issues a Cloud B token. Full audit trail — Workload A's identity is preserved across the boundary.WIMSE does not replace OAuth 2.0, OIDC, or TLS — it composes with them. This scenario maps six common enterprise workload identity flows to the standards teams reach for today, identifies the precise gap each leaves at scale, and shows which WIMSE primitive closes it.
Each row is a real flow. Current standard is what teams already have. WIMSE primitive is the composable layer that closes the gap — without replacing the existing investment.
| Flow | Current standard | Gap at scale | WIMSE primitive | Advantage |
|---|---|---|---|---|
| Workload-to-workload auth billing-svc → payment-svc |
RFC 8705 mTLS + RFC 6750 Bearer or shared client_secret |
mTLS proves cert identity, not application identity; Bearer tokens are ambient — stolen token = full access until expiry; no per-request URI binding | WIT (typ: wit+jwt) + WPT (typ: application/wpt+jwt) draft-ietf-wimse-workload-creds / draft-ietf-wimse-wpt |
Application-layer identity bound to workload's EC key; per-request proof locked to exact target URI — stolen WPT is worthless outside its intended call |
| OAuth client authentication workload authenticates to token endpoint |
RFC 6749 §2.3 client_secret or RFC 7523 private_key_jwt + client registration at every AS |
client_secret is a long-lived shared secret — single compromise affects all workload instances; private_key_jwt requires static key management and pre-registration at each AS | SPIFFE Client Auth draft-ietf-oauth-spiffe-client-auth |
WIT is the credential — AS validates against SPIFFE trust domain JWKS; zero pre-registration, zero shared secrets anywhere in the flow |
| Cross-domain token exchange cloud-a workload calls cloud-b API |
RFC 8693 generic token exchange or bilateral OIDC federation + pre-shared trust per AS pair |
RFC 8693 exchanges tokens without defining portable workload identity format; bilateral OIDC requires pre-registration between every pair of AS instances — O(N²) config at scale | Identity Chaining draft-ietf-oauth-identity-chaining (RFC 7523 + RFC 8693 profile) |
SPIFFE ID preserved in jwt-authz-grant sub; domain B needs only domain A's public key — N domains need N configs, not N² bilateral agreements |
| Multi-hop audit trail user → gateway → svc-A → svc-B |
Bearer token forwarding or unsigned Forwarded / X-Forwarded-For headers |
Forwarded Bearer is ambient — any intermediate can forge context; headers are unsigned and trivially spoofable; no cryptographic binding between request hops | Txn-Token + WPT tth claimdraft-ietf-oauth-transaction-tokens |
tth in every WPT is SHA-256 of the originating Txn-Token; forged intermediate hops are detectable; entire chain is cryptographically auditable |
| Identity federation at scale IdP-A trusts IdP-B across orgs |
SAML 2.0 metadata federation or OIDC point-to-point Discovery per-IdP bilateral registration |
SAML is human-centric and operationally heavyweight for machine identities; OIDC Discovery requires bilateral pre-registration for every IdP pair — does not scale to dynamic multi-cloud topologies | OID-FED 1.0 openid-federation-1_0 — Entity Configuration + trust chain resolution |
Each IdP publishes one Entity Configuration; trust established by path traversal through an authority hierarchy — no bilateral pre-registration; dynamic trust at runtime |
| Selective credential disclosure workload shares only needed claims |
Full JWT (all claims visible to every relay) or opaque token + introspection endpoint RFC 7662 |
Full JWT leaks every claim (trust domain, environment, role) to every relay in the chain; opaque introspection requires an online round-trip to the token endpoint per request | SD-WIT typ: wit+sd-jwt (base SD-JWT, not SD-JWT VC) |
Each relay receives only disclosed claims; no introspection endpoint required; fully offline validation; cryptographic proof of what was not disclosed |
spiffe://trust-domain/svc/billing) in sub, cryptographically signed by the IdP — portable across every layercnf.jwk in WIT) — stolen WPT is useless without the private keyaud = exact target URI; wth = SHA-256 of the WIT — SSRF and token reuse across endpoints are structurally impossiblejti replay store prevents double-submission within the windowtyp=wit+jwt; WPT profiles RFC 7519 with typ=application/wpt+jwt and adds wth (analogous to DPoP's ath in RFC 9449). Both are composed on top of existing OAuth/JWT infrastructure — no new wire protocol.
client_secret is a shared secret — stored in env vars or vaults; all workload instances share one secret; single leak compromises all instancesprivate_key_jwt (RFC 7523) is stronger but requires out-of-band client registration at every AS — static key management at each endpointclient_assertion with client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer — a standard RFC 7523 profileX-Caller-ID), which any hop can spoofsub; domain B validates with domain A's public key only — SPIFFE ID is preserved end-to-endtth = SHA-256(Txn-Token): every workload-to-workload hop proves it is part of the same originating transaction — forged intermediate hops are detectablejti uniqueness on every token in the chainAuthorization headers. Both compose cleanly with existing AS infrastructure.
/.well-known/openid-federation — one config, all trustmin(EC.exp, SS.exp) — typically 24h; stale chains refresh automatically without manual intervention