SSO Provider Setup
Curupira lets a tenant's users sign in with social / enterprise identity providers
(Google, Microsoft, GitHub, Discord, Meta). The whole login flow — authorize → provider
consent → callback → session, with PKCE, CSRF state, OIDC nonce, and id_token signature
verification — is already built. To turn a provider on for a tenant you only need to:
- Register an app with the provider and copy its client ID + client secret.
- Store those on the tenant's SSO connection (two ways, below).
- Register Curupira's callback URL in the provider console.
:::note The connection model
SSO connections are tenant-level, not application-level: there is one connection per
(tenant, provider), shared by all of that tenant's applications. The client_secret is
encrypted at rest (AES-256-GCM) and is write-only — no read ever returns it; responses
expose only a has_secret flag.
:::
Prerequisites
SSO_SECRET_ENC_KEYmust be configured on the server (the encryption key for stored secrets). The server is fail-closed: with no key, SSO saves are rejected rather than storing secrets in plaintext. This is already configured on QA.- The
sso.*permission. Setting keys needssso.create/sso.update(readingsso.read, deletingsso.delete). These are seeded to the super_admin role; a tenant admin gets them the normal way — via a role group that carries them or a direct role assignment. Without the permission the dashboard hides the controls and the API returns 403.
Callback / redirect URLs (register these EXACTLY)
Providers reject anything that doesn't match character-for-character.
| Provider | Redirect / Callback URL |
|---|---|
https://auth.curupira.api.br/sso/google/callback | |
| Microsoft | https://auth.curupira.api.br/sso/microsoft/callback |
| GitHub | https://auth.curupira.api.br/sso/github/callback |
| Discord | https://auth.curupira.api.br/sso/discord/callback |
| Meta | https://auth.curupira.api.br/sso/meta/callback |
Setting your SSO keys — two ways
Both flows write to the same tenant connection and require the same sso.* permission.
Way 1 — Dashboard UI
- Open the admin dashboard and sign in.
- Click SSO in the sidebar (the
/ssopage). - Choose your tenant from the tenant selector at the top of Provider connections.
- Click Add SSO Connection and fill the form:
- Provider — Google / Microsoft / GitHub / Discord / Meta (Facebook).
- Client ID and Client Secret — from the provider (the secret is a masked field).
- Scopes — space-separated; pre-filled with sensible per-provider defaults.
- Enabled — toggle on to make the tenant's login button live.
- Redirect / Callback URL — shown read-only; copy it into the provider console.
- Create Connection. The connection appears in the list with edit / delete actions. When editing, leave the Client Secret blank to keep the stored one (it is never returned), or enter a value to replace it.
Way 2 — Admin API
Same permission, same tenant-level model. Requests carry the admin session cookie.
| Method & path | Purpose |
|---|---|
GET /api/admin/sso-connections?tenant_id=<uuid> | List a tenant's connections |
POST /api/admin/sso-connections | Create a connection |
GET /api/admin/sso-connections/{id} | Get one connection |
PUT /api/admin/sso-connections/{id} | Update (secret optional; blank = keep) |
DELETE /api/admin/sso-connections/{id} | Delete a connection |
Create a connection:
curl -X POST https://admin.curupira.api.br/api/admin/sso-connections \
-H 'Content-Type: application/json' \
-b cookies.txt \
-d '{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "google",
"client_id": "1234567890-abc.apps.googleusercontent.com",
"client_secret": "GOCSPX-your-secret-here",
"scopes": ["openid", "email", "profile"],
"enabled": true
}'
The response echoes the connection without the secret — it carries has_secret: true
instead:
{
"id": "7f1c...",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "google",
"client_id": "1234567890-abc.apps.googleusercontent.com",
"scopes": ["openid", "email", "profile"],
"enabled": true,
"has_secret": true
}
Update (omit client_secret to keep the stored one) and delete:
# Rotate the secret + narrow scopes
curl -X PUT https://admin.curupira.api.br/api/admin/sso-connections/7f1c... \
-H 'Content-Type: application/json' -b cookies.txt \
-d '{"scopes": ["openid", "email"], "client_secret": "GOCSPX-new-secret", "enabled": true}'
# Delete
curl -X DELETE -b cookies.txt https://admin.curupira.api.br/api/admin/sso-connections/7f1c...
Default scopes per provider
Leave the scopes blank to accept these defaults, or override them.
| Provider | Type | Default scopes |
|---|---|---|
| OpenID Connect | openid email profile | |
| Microsoft | OpenID Connect | openid email profile |
| GitHub | OAuth2 (no id_token) | read:user user:email |
| Discord | OAuth2 (no id_token) | identify email |
| Meta | OAuth2 (no id_token) | email public_profile |
:::warning Meta app review
Meta requires Business Verification + App Review to obtain the email permission for
public (non-test) use — budget time for that. Microsoft's token issuer is tenant-specific; if
you register a single-tenant app, provide your directory (tenant) ID so id_token
verification can be made strict.
:::
Testing a connection
Once a connection is enabled, sign in via:
https://auth.curupira.api.br/sso/<provider>/authorize?tenant=<tenant-slug>
Complete the provider login and confirm you land back signed in.
:::note Account-linking policy If a provider returns an email that matches an existing password account, login is refused ("sign in with your password, then connect SSO in settings") — SSO never auto-takes-over an existing account. A brand-new email auto-provisions an SSO-only user. :::