Ridding ourselves of API keys
Swirls now exchanges JWTs from your identity provider for short-lived access tokens. No more long-lived API keys and no more guessing which user did what.
Agents are getting good at calling APIs. The credentials they call them with have not kept up. Most agent deployments still ship with a long-lived API key set in an environment variable, scoped to the workload's full capability, rotated when somebody remembers.
We wanted to rid ourselves of API keys. Swirls now exchanges JWTs from your identity provider for short-lived Swirls access tokens. There is no SWIRLS_API_KEY to issue, store, or rotate.
#Why this matters
Long-lived API keys are the leftover credential pattern from before workloads had identity. They live in environment variables and CI secrets, they get scoped broadly because the alternative is fiddly, and they survive the engineer who created them. The risk is structural: a key valid for a year is a key that can be exfiltrated for a year.
The fix is not better key rotation. It is moving the trust root to the identity provider you already operate, and replacing the year-long key with one that expires in minutes.
#How it works
OIDC Federation is a token exchange. Four steps:
- Your workload requests a JWT from its identity provider
- It presents the JWT to the Swirls token endpoint
- Swirls validates the JWT against the provider you registered, and returns a short-lived access token
- Your workload calls the Swirls API with that token as a bearer credential, then discards it
Each exchange is bound to one project registration. Projects can explicitly inherit an organization provider or register their own, and each binding has a unique audience. The token cannot reach sibling projects or administer the organization.
The exchanged token is short-lived (approximately 15 minutes) and there is no refresh token. When it expires, you mint a new IdP JWT and exchange again. There is no client secret in the exchange itself — the IdP's signature on the JWT is the credential.
The token endpoint:
curl -s -X POST https://auth.swirls.ai/oauth2/token \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
--data-urlencode "subject_token=$YOUR_IDP_JWT" \
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt"
In TypeScript, the same exchange with a small cache so you only mint a new token when the current one is within thirty seconds of expiring. This cache must belong to one subject and one project registration; create separate instances for different users or projects. The SDK token source handles refresh and concurrent calls:
let cached: { token: string; expiresAt: number } | null = null
async function getSwirlsToken(idpJwt: string): Promise<string> {
const now = Date.now()
if (cached && cached.expiresAt - now > 30_000) {
return cached.token
}
const res = await fetch("https://auth.swirls.ai/oauth2/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
subject_token: idpJwt,
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
}),
})
if (!res.ok) throw new Error(`Token exchange failed: ${(await res.json()).error}`)
const data = await res.json()
cached = { token: data.access_token, expiresAt: now + data.expires_in * 1000 }
return cached.token
}
Then call the Swirls API the way you would call any bearer-authenticated service:
curl -s https://swirls.ai/api/<endpoint> \
-H "Authorization: Bearer ${getSwirlsToken(idpJwt)}"
#Who did what
API keys flatten attribution. Every event in the system traces back to the holder of the key — in practice, the engineer who generated it or the workload it lives in. The end user who actually triggered the work disappears.
OIDC Federation keeps the IdP's user ID in the audit trail. The federation exchange records it as the subject; downstream events in the same session correlate back to that exchange:
EVENT SUBJECT KIND
exchange user_3EJlVyjn... federation_exchange
token minted auth execution_token
graph executed user_3EJlVyjn... graph
You can answer the questions a flat key can't: which user triggered which agent, which user accessed which secret, which user's session produced which graph execution. The subject claim from your IdP, the same one your application already trusts, propagates through the runtime's audit log.
#Standards and providers
OIDC Federation in Swirls implements RFC 8693 (OAuth 2.0 Token Exchange) and publishes RFC 8414 discovery metadata at https://auth.swirls.ai/.well-known/oauth-authorization-server. Clerk and Supabase are supported; both must use asymmetric JWT signing keys (RS256 for Clerk; RS256 or ES256 for Supabase). Supabase projects on the legacy shared HS256 secret cannot federate.
Inbound federation is configured in Cloud or the SDK. In .swirls files, role and policy blocks narrow access using verified claims. Outbound auth and connection blocks configure how a workflow calls third-party services; they do not register an inbound identity provider.
#Getting started
Three steps to register an identity provider:
- Open the target project's Auth section in Swirls Cloud. An organization owner/admin can first register a reusable provider on organization Auth.
- Select Inherit provider for an organization default, or Register provider for an independent Clerk or Supabase binding.
- Configure JWTs with that binding's Project audience in
audand the displayed Issuer iniss. Repeat for each project; an organization default alone cannot exchange tokens.
The full docs — JWT requirements, error responses, token expiry handling, and the token helper pattern in more languages — live at swirls.ai/docs/platform/oidc-federation.
