Enterprise SSO Endpoints
Create and manage SAML and OIDC identity provider connections, and initiate SSO authentication flows.
Create SSO Connection
POST /api/v1/sso/connections
Headers
Authorization: Bearer <apiKey>X-Bulwark-Tenant: <tenant-id>
The request body is a flat connection object. protocol and display_name are required; provide either the SAML or the OIDC fields depending on protocol.
Body — SAML
{
"protocol": "saml",
"display_name": "Acme Okta",
"email_domains": ["acme.com"],
"idp_entity_id": "http://www.okta.com/exk...",
"idp_sso_url": "https://acme.okta.com/app/.../sso/saml",
"idp_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"jit_provisioning": true,
"default_roles": ["user"],
"group_attribute": "groups",
"role_mappings": { "admin-group": "admin", "dev-group": "member" }
}
Body — OIDC
{
"protocol": "oidc",
"display_name": "Acme Azure AD",
"email_domains": ["acme.com"],
"oidc_issuer": "https://login.microsoftonline.com/{tenant-id}/v2.0",
"oidc_client_id": "...",
"oidc_client_secret": "...",
"oidc_scopes": ["openid", "profile", "email"],
"jit_provisioning": true,
"default_roles": ["user"],
"group_attribute": "groups",
"role_mappings": { "00000000-0000-0000-0000-000000000001": "admin" }
}
| Field | Required | Description |
|-------|----------|-------------|
| protocol | Yes | saml or oidc |
| display_name | Yes | Human-readable connection label |
| email_domains | Yes | Email domains that route to this connection |
| jit_provisioning | No | Auto-create users on first SSO login |
| default_roles | No | Roles assigned to JIT-provisioned users |
| group_attribute | No | IdP attribute carrying group membership |
| role_mappings | No | Map of IdP group → Bulwark role |
| oidc_client_secret | OIDC | Write-only; stored encrypted, never returned. Requires an encryption key to be configured. |
Response 201
Returns the created connection (same shape as Get SSO Connection):
{
"id": "sso_01j...",
"tenant_id": "ten_01j...",
"protocol": "saml",
"display_name": "Acme Okta",
"email_domains": ["acme.com"],
"status": "active",
"idp_entity_id": "http://www.okta.com/exk...",
"idp_sso_url": "https://acme.okta.com/app/.../sso/saml",
"jit_provisioning": true,
"default_roles": ["user"],
"role_mappings": { "admin-group": "admin" },
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
List SSO Connections
GET /api/v1/sso/connections
Headers
Authorization: Bearer <apiKey>X-Bulwark-Tenant: <tenant-id>
Response 200
{
"connections": [
{
"id": "sso_01j...",
"tenant_id": "ten_01j...",
"protocol": "saml",
"display_name": "Acme Okta",
"email_domains": ["acme.com"],
"status": "active",
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
],
"count": 1
}
Get SSO Connection
GET /api/v1/sso/connections/{id}
Retrieve a single SSO connection by ID.
Headers
Authorization: Bearer <apiKey>X-Bulwark-Tenant: <tenant-id>
Path Parameters
| Parameter | Description |
|-----------|-------------|
| id | SSO connection ID |
Response 200
{
"id": "sso_01j...",
"tenant_id": "01j...",
"protocol": "saml",
"display_name": "Acme Okta",
"email_domains": ["acme.com"],
"status": "active",
"idp_entity_id": "http://www.okta.com/exk...",
"idp_sso_url": "https://acme.okta.com/app/.../sso/saml",
"idp_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"idp_metadata_url": null,
"oidc_issuer": null,
"oidc_client_id": null,
"oidc_scopes": null,
"jit_provisioning": true,
"default_roles": ["user"],
"group_attribute": null,
"role_mappings": {},
"created_at": "2026-03-30T00:00:00Z",
"updated_at": "2026-03-30T00:00:00Z"
}
OIDC-specific fields (oidc_issuer, oidc_client_id, oidc_scopes) are omitted for SAML connections; SAML-specific fields (idp_entity_id, idp_sso_url, idp_certificate, idp_metadata_url) are omitted for OIDC connections. The encrypted client secret is never returned.
Response 404
{ "error": "SSO connection not found" }
Delete SSO Connection
DELETE /api/v1/sso/connections/{id}
Removes the connection. Users who authenticated via this connection are not deleted, but future SSO logins through it will fail.
Headers
Authorization: Bearer <apiKey>X-Bulwark-Tenant: <tenant-id>
Response 200
{ "status": "deleted" }
Detect SSO by Email Domain
POST /api/v1/auth/sso/detect
Public endpoint. Given an email address, returns the SSO connection that should handle it. Used by login UIs to redirect directly to the correct IdP without exposing connection IDs.
Headers
X-Bulwark-Tenant: <tenant-id>
Body
{
"email": "[email protected]"
}
Response 200 — Connection Found
{
"sso_required": true,
"connection_id": "sso_01j...",
"protocol": "saml",
"display_name": "Acme Okta",
"authorize_url": "/api/v1/auth/sso/sso_01j.../authorize"
}
Response 200 — No SSO Connection
When no connection matches the email domain, the endpoint still returns 200 (not 404) with sso_required: false, so login UIs can branch cleanly.
{
"sso_required": false
}
Initiate SSO Flow
GET /api/v1/auth/sso/{id}/authorize
Redirects the user's browser to the IdP. For SAML, issues a signed AuthnRequest. For OIDC, redirects to the IdP's authorization endpoint.
Query Parameters
| Parameter | Description |
|-----------|-------------|
| redirect_uri | Where Bulwark should redirect after successful authentication |
| state | Opaque value to prevent CSRF; returned unchanged |
| login_hint | Pre-fill the user's email in the IdP login form |
Response
302 Found — redirect to IdP.
OIDC Callback
GET /api/v1/auth/sso/{id}/callback
Handles the authorization code callback from an OIDC IdP. Exchanges the code for tokens, provisions or updates the user, and issues a Bulwark session.
Query Parameters
| Parameter | Description |
|-----------|-------------|
| code | Authorization code from IdP |
| state | State value from the original authorize request |
| error | Error code if IdP rejected the request |
Response
302 Found — redirect to the redirect_uri supplied at authorize time, with a short-lived session_code query parameter for the client to exchange.
SAML Assertion Consumer Service (ACS)
POST /api/v1/auth/sso/{id}/acs
Receives the SAML response (HTTP POST binding) from the IdP. Validates the assertion signature, processes attribute statements, provisions or updates the user, and issues a Bulwark session.
Body
application/x-www-form-urlencoded
| Field | Description |
|-------|-------------|
| SAMLResponse | Base64-encoded SAML response |
| RelayState | Opaque value passed from the original AuthnRequest |
Response
302 Found — redirect to the application with session.