Sign in with CAIRL (OpenID Connect)
Add Sign in with CAIRL to your application through a standard OpenID Connect integration, with Clerk and Auth0 walkthroughs.
What this is
Add Sign in with CAIRL through a compatible OpenID Connect integration. CAIRL sends a pseudonymous per-partner identifier and authentication information, not your name, email address, identity documents, age, or verification status. Verification is a separate, authorized service.
Use this when your application already runs an identity platform and you
want Sign in with CAIRL without writing protocol code. Walkthroughs below
cover Clerk's custom social connection and Auth0's generic OpenID Connect
connection; Okta and similar OpenID Connect clients are expected to work but
are not promised until tested. If you want verified claims such as age or
identity, use the OAuth and OIDC guide
instead. The two purposes never mix: a sign-in request cannot carry a
verification scope, and a verification request cannot carry openid.
Discovery
Point your identity platform at the discovery document for the environment you integrate against. Every value it advertises is implemented; nothing else is.
| Environment | Discovery URL |
|---|---|
| Production | https://cairl.app/.well-known/openid-configuration |
| Staging | https://staging.cairl.app/.well-known/openid-configuration |
The issuer in each document equals that origin, and every ID token's iss
equals the issuer you discovered. Signing keys are published at
/.well-known/jwks.json on the same origin (RS256, rotated by publishing
the next key before it signs).
Register your application
An Owner or Admin of your CAIRL business context does two things in the dashboard. There is no self-service registration.
- Create a sign-in credential on
/home/f/{slug}/keyswith the Log in with CAIRL option checked (that is the dashboard's label today; it marks the credential as sign-in enabled). A sign-in credential carriesopenidand nothing else — one key per consent shape, so a sign-in key never carries verification claims. You receive aclient_idand a separately generatedclient_secret. The secret is shown once; store it only on your server (or in your identity platform's secret field). - Register your callback URL on
/home/f/{slug}/connect, exactly as your identity platform reports it. Matching is strict.
One host per sign-in-enabled context. CAIRL issues a different sub for
each partner (pairwise subjects), so all redirect URIs registered for a
sign-in-enabled context must share one host. A second host is refused at
registration. Use a separate business context for a second application.
The authorization request
Your platform redirects the User to the authorization endpoint (GET or POST) with:
| Parameter | Value |
|---|---|
response_type | code |
client_id | Your client ID |
redirect_uri | A registered callback URL |
scope | openid, alone. Optional: a sign-in key prescribes it |
state | Random value, returned unchanged |
code_challenge | PKCE S256 challenge (required) |
code_challenge_method | S256 |
nonce | Optional. When sent, it is echoed byte-for-byte in the ID token |
prompt | Optional. none, login, or consent |
max_age | Optional. Seconds since the User last authenticated to CAIRL |
The User signs in to CAIRL if needed (or again, when prompt=login or
max_age requires it), sees a consent screen naming your application and
stating exactly what is shared, and approves or denies. prompt=none never
shows a screen: it returns login_required or consent_required to your
callback instead.
The token exchange
Exchange the code at the token endpoint with one client authentication method:
client_secret_basic: anAuthorization: Basicheader, orclient_secret_post:client_idandclient_secretin the form body.
A request that uses both is rejected. The response contains:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid",
"id_token": "…"
}What the ID token contains
Exactly these claims, and never anything else:
| Claim | Meaning |
|---|---|
iss | The issuer you discovered |
sub | The User's pairwise identifier for your partner. Stable across sign-ins |
aud | Your client_id |
iat | Issued at |
exp | Five minutes after iat |
auth_time | When the User authenticated to CAIRL |
at_hash | Hash binding the ID token to the access token |
nonce | Only when you sent one |
amr | Only when known: pwd, otp, swk, or mfa |
The ID token never contains a name, email address, identity document, age, verification status, or any other CAIRL claim.
What userinfo returns
For a sign-in token, GET or POST /api/oauth/userinfo with the bearer
access token returns exactly:
{ "sub": "pws_v1_…" }An ID token presented as a bearer token is rejected.
Errors
| Where | Code | Meaning |
|---|---|---|
| Authorization | invalid_scope | openid was combined with another scope, or the credential is not sign-in enabled |
| Authorization | invalid_request | Missing or unsupported PKCE, conflicting prompt, bad max_age |
| Authorization | login_required | prompt=none and the User is not signed in, or max_age is not met |
| Authorization | consent_required | prompt=none and consent has not been given |
| Authorization | access_denied | The User denied consent |
| Token | invalid_client | Wrong or missing client authentication |
| Token | invalid_request | Both client authentication methods in one request |
| Token | invalid_grant | Expired, replayed, or mismatched code, or the grant was revoked |
| Token | temporarily_unavailable | Signing is unavailable. Restart the flow |
Nothing is ever sent to an unregistered callback URL.
Revocation
A User can remove your application from their CAIRL connected-apps list at any time. Revocation blocks the next authorization, token exchange, and userinfo use for that User. It does not sign the User out of your application's own session, and an already-issued ID token remains valid until its five-minute expiry.
Clerk walkthrough (custom social connection)
Targeting Clerk's custom social connection. In your Clerk dashboard:
-
Open Configure → SSO connections, choose Add connection → For all users, and pick Custom OIDC provider.
-
Fill the fields:
Clerk field Value Name CAIRLKey cairlDiscovery endpoint The discovery URL for your environment (table above) Client ID Your CAIRL client ID Client Secret Your CAIRL client secret Scopes openid -
Copy the Callback URL Clerk shows for the connection and register it on
/home/f/{slug}/connect. -
Enable the connection. Clerk's hosted sign-in page now offers Continue with CAIRL (Clerk controls that label).
Expected result: the User is sent to CAIRL, signs in, approves the consent
screen, and returns to your application signed in. Clerk stores the sub as
the external account identifier. Signing in again returns the same sub. Clerk
receives no name or email from CAIRL, so configure Clerk to collect any profile
fields your application needs.
Auth0 walkthrough (generic OpenID Connect connection)
Reproduction evidence pending. This walkthrough was written against Auth0's published documentation as of 2026-09-17. Live reproduction on a real Auth0 development tenant is recorded on Linear issue BLD-2996; until that evidence is posted, treat any step Auth0's dashboard presents differently as a documentation gap to report, not as a CAIRL fault.
Before you start: what Auth0 sends
CAIRL requires PKCE (S256) and exactly one client authentication method.
Auth0's generic OpenID Connect enterprise connection meets both, provided it is
configured as follows. Each fact carries the Auth0 page it was taken from.
| Requirement | Auth0 behavior |
|---|---|
| Authorization code | Set the connection Type to Back Channel. Auth0 documents Back Channel as response_type=code, and Front Channel as response_type=id_token with response_mode=form_post. CAIRL's discovery document advertises only code and query, so a Front Channel connection cannot complete. (Connect to OpenID Connect Identity Provider) |
PKCE S256 | The connection's pkce setting defaults to auto, which Auth0 documents as "uses the strongest algorithm available" from the provider's discovery document. CAIRL advertises code_challenge_methods_supported: ["S256"], so auto resolves to S256. Set it to s256 explicitly if you prefer no inference. disabled makes every sign-in fail with invalid_request. (Configure PKCE and Claim Mapping for OIDC Connections) |
| Client authentication | Auth0's connection token_endpoint_auth_method accepts client_secret_post or private_key_jwt. Use client_secret_post; CAIRL accepts it. client_secret_basic is not an Auth0 connection option and is not needed, and CAIRL does not support private_key_jwt. (Management API: Create a connection) |
openid alone | Auth0 requires at least openid in the connection's scopes. CAIRL requires openid and nothing else, so remove profile, email, or anything Auth0 pre-fills. Any extra scope returns invalid_scope. (Connect to OpenID Connect Identity Provider) |
| Claims source | Auth0 does not call the provider's userinfo endpoint for this connection type; it reads claims from the ID token. CAIRL's ID token carries only the claims listed above, so the Auth0 user profile receives a sub and no name or email. (Connect to OpenID Connect Identity Provider) |
Steps
In your Auth0 dashboard:
-
Open Authentication → Enterprise → OpenID Connect and choose Create Connection.
-
Fill the fields:
Auth0 field Value Connection name cairl(this becomes part of every Auth0user_id, so choose it once)Issuer URL https://cairl.app/.well-known/openid-configurationfor production, or the staging discovery URL from the table aboveType Back Channel Client ID Your CAIRL client ID Client Secret Your CAIRL client secret Scopes openidAuth0 reads the rest of the configuration (endpoints, JWKS, PKCE method) from the discovery document.
-
Register the callback URL Auth0 uses for every enterprise connection on
/home/f/{slug}/connect:https://{your-auth0-domain}/login/callback{your-auth0-domain}is the tenant domain shown in your Auth0 settings (for exampleexample.us.auth0.com), or your Auth0 custom domain if you have one. Register the domain your users will actually be sent through: a tenant domain and a custom domain are two hosts, and a sign-in-enabled context may register only one. -
On the connection's Applications tab, enable it for the application that should offer Sign in with CAIRL. If you use Auth0's Universal Login, turn on Display connection as a button and set the button text to
Sign in with CAIRL.
Expected result: the User is sent to CAIRL, signs in, approves the consent
screen, and returns to your application signed in. Auth0 creates the user as
oidc|cairl|{sub}, where {sub} is CAIRL's pairwise identifier (Auth0 staff
confirm the {strategy}|{connection name}|{provider user id} shape for
enterprise connections on the
Auth0 Community;
verify it on your tenant). Signing in again returns the same sub, so the
same Auth0 user. Leave the connection's attribute mapping at its default;
there is nothing in the ID token to map besides sub. Auth0 receives no name
or email from CAIRL, so collect any profile fields your application needs in
your own application or through Auth0 progressive profiling.
Not included
Refresh tokens, profile and email scopes, sign-out (RP-initiated logout),
dynamic client registration, private_key_jwt client authentication, sandbox
OpenID Connect, and formal certification are not part of this integration. Ask
before building on any of them.