OIDC Federation
Exchange a JWT from your own identity provider for a short-lived Swirls access token. No long-lived API keys required.
OIDC Federation lets your workload authenticate to Swirls using a JWT issued by your own identity provider. Your workload exchanges that JWT at the Swirls token endpoint and receives a short-lived Swirls access token. Use that token to call the Swirls API.
No long-lived Swirls API keys to store or rotate. Your organization keeps full control at its own identity provider.
How it works
Your workload requests a JWT from your identity provider (Clerk or Supabase). It presents that JWT to the Swirls token endpoint. Swirls verifies the JWT against the registered provider configuration and returns a short-lived access token. Your workload uses that access token to call the Swirls API, then discards it. When the token expires, repeat the exchange.
Configure a provider
Open the Auth section in the Portal
In the Swirls Portal, select your organization. In the left sidebar, open Auth.
Register a provider
Click Register provider. Select your identity provider. Swirls currently supports Clerk and Supabase.
Enter the instance identifier
The identifier is not a URL. Enter the value that matches your provider.
Enter your Frontend API domain.
| Environment | Format | Example |
|---|---|---|
| Production | clerk.yourdomain.com | clerk.acme.com |
| Development | your-app-name.clerk.accounts.dev | acme-dev.clerk.accounts.dev |
Find this in the Clerk dashboard under API Keys.
Enter your project reference. This is the 20-character string from your Supabase project URL and project settings.
Example: abcdefghijklmnopqrst
Find this in the Supabase dashboard under Project Settings > General.
Set the subject claim (optional)
Swirls reads the subject from the sub claim by default. If your tokens carry the subject in a different claim, enter that claim name here.
Save and copy the provider values
After saving, the provider appears in the list with a Pending status. It becomes Active automatically after the first successful token exchange.
Copy the two values shown:
- Audience: a unique string like
swirls:aud:9fK2x7.... Your JWTs must include this in theiraudclaim. - Issuer: the
issvalue your JWTs must carry. This is derived automatically from your instance identifier. It is shown here for reference.
Configure your identity provider to include the Swirls audience
Your JWTs must carry the Swirls audience in the aud claim. You can add it alongside any audiences already present.
Create a JWT Template in the Clerk dashboard.
In the template's claims, set:
{
"aud": "swirls:aud:9fK2x7..."
}Replace the audience value with the one copied from the Portal. Mint tokens against this template when your workload needs to call Swirls.
Your project must use asymmetric JWT signing keys (RS256 or ES256), which publish a JWKS that Swirls verifies against. Projects still on the legacy shared HS256 secret are not supported. See Supabase JWT signing keys.
To add the Swirls audience, use a Custom Access Token Hook that adds the audience value to the aud claim. You can keep any existing audiences and add the Swirls value alongside them.
The aud claim must include the Swirls audience value copied from the Portal.
Exchange a JWT for a Swirls access token
Swirls implements RFC 8693 OAuth 2.0 Token Exchange.
Endpoint: POST https://auth.swirls.ai/oauth2/token
Content-Type: application/x-www-form-urlencoded
| Parameter | Value |
|---|---|
grant_type | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Your IdP-issued JWT |
subject_token_type | urn:ietf:params:oauth:token-type:jwt |
No client ID or client secret. The subject token authenticates the request.
JWT requirements
Before exchanging, verify your JWT satisfies all of these:
issmatches the issuer shown in the Portal for your registered provider.audcontains the Swirls audience (swirls:aud:...).- The subject claim (default
sub) is non-empty. expis in the future.- The signature uses an asymmetric algorithm (RS256 for Clerk; RS256 or ES256 for Supabase) and verifies against your IdP's public keys.
Example
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"Successful response
HTTP 200:
{
"access_token": "...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 900
}The access_token is an opaque bearer token. Do not parse or inspect it. Present it directly in API requests.
Tokens are short-lived (approximately 15 minutes). There is no refresh token. When the token expires, mint a fresh JWT from your IdP and exchange again.
Token helper pattern
Wrap the exchange in a helper that caches the token until near expiry:
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) {
const err = await res.json()
throw new Error(`Token exchange failed: ${err.error}`)
}
const data = await res.json()
cached = {
token: data.access_token,
expiresAt: now + data.expires_in * 1000,
}
return cached.token
}This follows the same pattern as AWS and GCP workload credential helpers: exchange once, cache until near expiry, then re-exchange.
Discovery
Swirls publishes OAuth 2.0 authorization server metadata at:
GET https://auth.swirls.ai/.well-known/oauth-authorization-serverThis returns an RFC 8414 metadata document listing the token endpoint and the supported token-exchange grant. Standard OAuth client libraries can discover the endpoint from this URL.
Error reference
Errors are returned as standard OAuth JSON error objects. Error details are intentionally opaque to avoid leaking information about token validation.
| HTTP | error | Cause |
|---|---|---|
| 415 | invalid_request | Wrong Content-Type. Use application/x-www-form-urlencoded. |
| 400 | unsupported_grant_type | grant_type is not the token-exchange URN. |
| 400 | invalid_request | Missing or malformed subject_token or subject_token_type. |
| 400 | invalid_grant | Unknown issuer, invalid signature, wrong or missing audience, expired token, replayed token, or missing subject claim. |
| 500 | server_error | Unexpected server error. |
Authenticate to Swirls endpoints
Present the access token as a bearer token in every API request:
Authorization: Bearer <access_token>The Swirls API base URL is https://swirls.ai/api. This is the same mechanism the Swirls CLI uses internally.
The token grants access scoped to the organization that registered the identity provider.
Example
curl -s https://swirls.ai/api/<endpoint> \
-H "Authorization: Bearer $SWIRLS_ACCESS_TOKEN"See the API reference for available endpoints and their paths.
Handling token expiry
The token expires after approximately 15 minutes. API requests with an expired token receive a 401 Unauthorized response.
On a 401, mint a fresh JWT from your identity provider and exchange it for a new access token. The token helper pattern above handles this automatically when you call getSwirlsToken before each request.
Next steps
- API reference: all available API namespaces and methods.
- SDK reference: typed TypeScript client.
- Swirls Cloud: managed runtime and Portal.