oidcfederation

Stop storing cloud keys. Prove identity instead.

When a hosting platform is breached, every customer rotates every secret it stored. The root cause is the same each time: a stored key is a copy, and copies leak. OIDC workload identity federation replaces the copy with a short-lived, signed proof of who the workload is. This page shows how it works, what changes in a breach, and introduces an open-source issuer for Cloudflare Workers.

See how it works Deploy the issuer

The problem: a key is a copy

A static credential grants access to whoever holds it. To use it, you copy it to every place that needs it. Each copy is a full credential with no expiry, and the cloud cannot tell them apart.

One cloud key copied to four placesAn AWS access key is copied to a laptop, a CI runner, platform environment variables and a backup. Every copy reaches the full AWS account. The platform copy is marked as breached.AWS access keyAKIA…7XQvalid until rotatedLaptop.env on diskCI runnerpipeline secretsPlatform env varshosting platform secretsBackup / exportforgotten copyplatform breachedYour AWS accountevery permission ofthe key, from anywhereFour copies, one credential. The attacker who read the platform copy has the same access as the other three.

What leaks

A platform stores your secrets as environment variables. One breach of that store exposes the keys of every customer at once.

What you have to do

Find every key the platform held, rotate it at each provider, update every other copy, then redeploy every service that reads it.

Why it hurts

The attacker can use a key the moment it is read. Rotation starts only when you learn of the breach, often days later.

How federation works

The workload holds no cloud key. It asks a trusted issuer for a signed statement of who it is. The cloud verifies the signature against the issuer’s public keys and hands out temporary credentials. Hover a step to follow it in the diagram.

Five steps of a federated credential exchangeA sequence diagram with three participants: the workload, the issuer and the cloud. The workload asks the issuer for a token, receives a signed JWT, sends it to the cloud, the cloud fetches the issuer's public keys, and returns temporary credentials.Your Workerholds no cloud keyIssueryour Cloudflare accountCloudAWS · GCP · Azure · Vault1getToken({ aud })2signed JWT · expires in 300 s3AssumeRoleWithWebIdentity(token)4GET /.well-known/jwks.json (cached)5temporary credentials · 900 s
  1. The workload asks the issuer for a token

    Over a private service binding. Its identity (sub) and allowed audiences (aud) were fixed at deploy time, so the request cannot change who it is.

  2. The issuer signs a short-lived JWT

    RS256, signed with a private key kept in the issuer’s Secrets Store. Claims: iss, sub, aud, an expiry (300 s by default, 1 hour at most) and a unique id.

  3. The workload exchanges the token with the cloud

    AssumeRoleWithWebIdentity at AWS, a token exchange at GCP, Azure or Vault. The token travels once, in a POST body.

  4. The cloud verifies the signature

    It verifies the signature with the issuer’s public keys from /.well-known/jwks.json, cached for up to an hour. It checks iss against the registered provider, exp against the clock, and sub and aud against the role’s trust policy.

  5. The cloud returns temporary credentials

    Scoped to one role, valid for the session length the role allows: 15 minutes in this example, up to 12 hours at AWS. Nothing long-lived was ever stored.

Anatomy of the token

A JSON Web Token: three base64url parts joined by dots. The first two are plain JSON that anyone can read. The third proves the issuer wrote them.

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImsyMDI2MTAyMjBhMWIifQ.eyJpc3MiOiJodHRwczovL29pZGMuZXhhbXBsZS5jb20iLCJzdWIiOiJiaWxsaW5nLXdvcmtlciIsImF1ZCI6InN0cy5hbWF6b25hd3MuY29tIiwiaWF0IjoxNzkyNDYwODAwLCJuYmYiOjE3OTI0NjA4MDAsImV4cCI6MTc5MjQ2MTEwMCwianRpIjoiNWQ3ZTk4ZjEtNmM0YS00ZTJiLWIwYWYtMmI0YzM4YzAxZTU4In0.QmVjYXVzZSB0aGUgaXNzdWVyIHNpZ25lZCBpdCwgdGhlIGNsb3VkIGNhbiB0cnVzdCBpdC4

header: alg, typ and kid, the issuer key that signed it payload: the claims below signature: RS256 over the first two parts
iss
https://oidc.example.com
Who signed it. The cloud fetches this issuer’s public keys.
sub
billing-worker
Who the workload is. Fixed at deploy time, not by the request.
aud
sts.amazonaws.com
Who may accept it. One audience per token.
iat · nbf · exp
300 s by default
Lifetime, 1 hour at most. After exp the token is just bytes.
jti
random UUID
Unique id, so a replayed token can be spotted.

What the cloud checks

The role trusts one issuer, one subject, one audience. A valid token for any other workload assumes nothing. This is the AWS form; GCP, Azure and Vault express the same three pins.

trust-policy.jsonjson
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.example.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.example.com:aud": "sts.amazonaws.com",
        "oidc.example.com:sub": "billing-worker"
      }
    }
  }]
}

What changes in a breach

In a platform breach with stored keys, the attacker walks away with working credentials. With federation, the same read yields nothing that works tomorrow.

  • Static keyvalid until rotated: days, months, years
  • Cloud credentials from a federated exchange15 min to 1 h typical, one role
  • Issuer token5 min by default, one identity, one audience

Bar length is illustrative, not to scale. A static key has no window: it works until someone finds and rotates it.

Static keyOIDC federation
What the workload storesA long-lived secretNothing. It asks at runtime.
Lifetime of what leaksUntil someone rotates itToken: 5 min by default. Cloud credentials: typically 15 min to 1 h.
Scope of what leaksEvery permission of the key, from anywhereOne identity, one audience, one role
CopiesLaptop, CI, platform, backupsNone. The private key stays in the issuer.
RotationManual, every copy, under pressureIssuer keys rotate at the issuer, by hand or on a cron. Workloads do not change.
Platform breachAttacker may hold working keysNo stored cloud key to take. Intercepted tokens and credentials expire within the hour. Protect the issuer signing key.
AuditA key looks the same from any callerEvery exchange is logged with its sub

Who already runs this way

Every one of these mints an identity token for a running job, and AWS, GCP, Azure, Vault, Claude and OpenAI accept it. Cloudflare Workers has no built-in identity token. Teams on Workers fall back to stored keys.

  • GitHub Actions
  • GitLab CI
  • Kubernetes
  • Vercel
  • Buildkite
  • CircleCI
  • HCP Terraform
  • Cloudflare Workers: none built in

Open source · MIT

workers-oidc-issuer

An OIDC workload-identity issuer that deploys into your own Cloudflare account. Zero runtime dependencies in the signer, a guided CLI for setup and key rotation, and provider guides for AWS, GCP, Azure, Vault, the Claude API and OpenAI.

Identity from deploy time, not from the request

A consumer Worker declares sub and aud in its wrangler.jsonc, as props on the service binding. Only a deployer can set them. The signer reads ctx.props. No request parameter can change who you are.

Signing is not on the internet

The signing endpoint is an RPC entrypoint reachable only over a service binding inside your account. The public surface is two GET routes: discovery and JWKS.

Keys in Secrets Store, rotation built in

Manual mode: keys are generated on your machine, uploaded over stdin, rotated with Add → Promote → Remove in the CLI. Auto mode: a Durable Object generates keys inside Cloudflare and rotates on a cron. Private keys never land in Durable Object storage.

Small, hardened, tested

Zero runtime dependencies in the signer. no_nodejs_compat, disable_ctx_exports, an exact JWK member allow-list, non-extractable keys, a TTL ceiling, 156 tests and a written threat model.

wrangler.jsoncjsonc
// billing-worker/wrangler.jsonc
// Identity is set here, by whoever deploys. No request can change it.
"services": [
  {
    "binding": "OIDC",
    "service": "oidc-issuer",
    "entrypoint": "Issuer",
    "props": { "sub": "billing-worker", "aud": ["sts.amazonaws.com"] }
  }
]

Two ways to hold the keys

Manual is the default and needs no Cloudflare API token. Auto rotates on its own in exchange for one scoped token.

Manual (default)Auto (opt-in)
Who generates keysYou, on your machine, in memoryA Durable Object, inside Cloudflare
Where keys travelstdin to wrangler, then Secrets StoreNever leave Cloudflare
RotationAdd, Promote, Remove in the CLIA cron, on a schedule you set
Cloudflare API tokenNoneRequired: Secrets Store Edit, this account only
Long-lived credential in the systemNoneThe API token. A leak means denial of service, not forgery.

Questions

  • Same standard, different job. Login OIDC proves who a person is to an app. Workload identity federation proves who a program is to a cloud provider. There is no user, no browser, no consent screen: a workload gets a signed token and exchanges it for cloud credentials.

Built by CloudRaker

We run document APIs on Cloudflare Workers and wanted the workload identity GitHub Actions already has. The issuer is the result. If you ship on Workers, deploy it and stop storing cloud keys.