Skip to main content

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:

  1. Register an app with the provider and copy its client ID + client secret.
  2. Store those on the tenant's SSO connection (two ways, below).
  3. 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_KEY must 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 needs sso.create / sso.update (reading sso.read, deleting sso.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.

ProviderRedirect / Callback URL
Googlehttps://auth.curupira.api.br/sso/google/callback
Microsofthttps://auth.curupira.api.br/sso/microsoft/callback
GitHubhttps://auth.curupira.api.br/sso/github/callback
Discordhttps://auth.curupira.api.br/sso/discord/callback
Metahttps://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

  1. Open the admin dashboard and sign in.
  2. Click SSO in the sidebar (the /sso page).
  3. Choose your tenant from the tenant selector at the top of Provider connections.
  4. 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.
  5. 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 & pathPurpose
GET /api/admin/sso-connections?tenant_id=<uuid>List a tenant's connections
POST /api/admin/sso-connectionsCreate 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.

ProviderTypeDefault scopes
GoogleOpenID Connectopenid email profile
MicrosoftOpenID Connectopenid email profile
GitHubOAuth2 (no id_token)read:user user:email
DiscordOAuth2 (no id_token)identify email
MetaOAuth2 (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. :::