SWIRLS_
Platform

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.

EnvironmentFormatExample
Productionclerk.yourdomain.comclerk.acme.com
Developmentyour-app-name.clerk.accounts.devacme-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 their aud claim.
  • Issuer: the iss value 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

ParameterValue
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
subject_tokenYour IdP-issued JWT
subject_token_typeurn: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:

  • iss matches the issuer shown in the Portal for your registered provider.
  • aud contains the Swirls audience (swirls:aud:...).
  • The subject claim (default sub) is non-empty.
  • exp is 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-server

This 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.

HTTPerrorCause
415invalid_requestWrong Content-Type. Use application/x-www-form-urlencoded.
400unsupported_grant_typegrant_type is not the token-exchange URN.
400invalid_requestMissing or malformed subject_token or subject_token_type.
400invalid_grantUnknown issuer, invalid signature, wrong or missing audience, expired token, replayed token, or missing subject claim.
500server_errorUnexpected 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

On this page