Single Sign-On via OIDC
Connect enterprise identity providers using OpenID Connect (OIDC) / OAuth 2.0.
All management endpoints require Authorization: Bearer <admin-jwt>.
Connection Management
POST /api/admin/organizations/:org_uuid/oidc-connections
Creates a new OIDC connection for an organization.
Input: ~~~json { "name": "Acme Google Workspace", "provider": "google", "client_id": "your-google-client-id", "client_secret": "your-google-client-secret", "discovery_url": "https://accounts.google.com/.well-known/openid-configuration", "allowed_domains": ["acme.com"] } ~~~
Output: ~~~json { "connection_uuid": "conn-uuid-123", "redirect_uri": "https://api.buttrbase.com/auth/oidc/conn-uuid-123/callback" } ~~~
Register the redirect_uri in your identity provider's application settings.
GET /api/admin/organizations/:org_uuid/oidc-connections
Lists all OIDC connections for an organization.
DELETE /api/admin/organizations/:org_uuid/oidc-connections/:connection_uuid
Deletes an OIDC connection.
Login Flow
GET /api/auth/oidc/:connection_uuid/authorize
Initiates the OIDC login flow. Redirect the user's browser to this URL.
Query Parameters:
redirect_uri— where to send the user after login.state— opaque value for CSRF protection (recommended).- Standard B2B sign-in with corporate IdPs
- Anything where SAML feels too heavy
Response: 302 redirect to the identity provider's authorization endpoint.
GET /api/auth/oidc/:connection_uuid/callback
The callback endpoint that handles the authorization code from the identity provider.
On success: Redirects to your redirect_uri with a session_token query parameter.
SDK Examples
Python
~~~python url = client.oidc_authorize_url("conn-uuid-123")
redirect user to url["redirect_url"]
On callback:
resp = client.oidc_callback({"code": "auth-code", "state": "state-value"}) ~~~Node.js / TypeScript
~~~typescript const url = await client.oidcAuthorizeUrl('conn-uuid-123'); // redirect user to url.redirect_url
// On callback: const resp = await client.oidcCallback('conn-uuid-123', { code: 'auth-code', state: 'state-value' }); ~~~
Go
~~~go url, err := client.OidcAuthorizeURL(ctx, "conn-uuid-123") // redirect user to url["redirect_url"]
// On callback: resp, err := client.OidcCallback(ctx, "conn-uuid-123", map[string]any{"code": "auth-code"}) ~~~
Rust
~~~rust let auth = client.oidc_authorize_url("conn-uuid-123").await?; // redirect user to auth["redirect_url"]
// On callback: let mut params = std::collections::HashMap::new(); params.insert("code".into(), "auth-code".into()); let result = client.oidc_callback(¶ms).await?; ~~~
Endpoint Summary
| Method | Path | Purpose |
|---|---|---|
| GET | /api/auth/oidc/{connection_uuid}/authorize | 302 redirect to IdP |
| GET | /api/auth/oidc/{connection_uuid}/callback | Handle IdP callback |
| POST | /api/admin/organizations/{org}/oidc-connections | Create connection |
| GET | /api/admin/organizations/{org}/oidc-connections | List connections |
| DELETE | /api/admin/organizations/{org}/oidc-connections/{uuid} | Delete connection |
When to Use
Error Reference
| Status | Code | Meaning |
|--------|------|---------|
| 400 | invalid_connection | OIDC connection ID does not exist |
| 400 | invalid_state | OAuth state parameter mismatch (CSRF) |
| 400 | missing_code | Authorization code not present in callback |
| 401 | token_exchange_failed | Provider rejected the code exchange |
| 403 | domain_mismatch | Email domain not allowed for this connection |
| 404 | org_not_found | org_uuid does not exist |
| 502 | provider_error | Identity provider returned an error |