Skip to content

Single Sign-On (SSO)

Breeze RMM supports organization-level single sign-on through OpenID Connect (OIDC) and SAML 2.0. SSO allows users to authenticate with their existing identity provider (IdP) instead of managing separate Breeze passwords. Each organization can configure one active SSO provider and optionally enforce SSO-only login to disable password-based authentication entirely.


ConceptDescription
SSO ProviderA configured identity provider (IdP) record tied to a single organization.
Provider typeoidc (OpenID Connect) or saml (SAML 2.0).
Provider statusinactive (default on creation), testing, or active. Only active providers are used for login.
Auto-provisioningWhen enabled (default), users who authenticate via SSO are automatically created in Breeze if they do not already exist.
SSO enforcementWhen enforceSSO is true, password-based login is disabled for the organization. Users must authenticate through the IdP.
Allowed domainsComma-separated list of email domains. If set, only users with matching email domains can authenticate via SSO.
Attribute mappingMaps IdP claim names to Breeze user fields (email, name, firstName, lastName, groups).
PKCEProof Key for Code Exchange. Breeze uses PKCE (S256 method) for all OIDC authorization requests to prevent authorization code interception.
Provider presetsBuilt-in configurations for common IdPs (Azure AD, Okta, Google Workspace, Auth0) that pre-fill scopes, attribute mappings, and endpoint URLs.

Breeze ships with built-in presets that auto-configure scopes, attribute mappings, and discovery URLs:

Preset IDProviderIssuer TemplateScopes
azure-adMicrosoft Azure ADhttps://login.microsoftonline.com/{tenant}/v2.0openid profile email
oktaOktahttps://{domain}.okta.comopenid profile email groups
googleGoogle Workspacehttps://accounts.google.comopenid profile email
auth0Auth0https://{domain}.auth0.comopenid profile email
Preset IDProviderSSO URL Template
azure-ad-samlMicrosoft Azure AD (SAML)https://login.microsoftonline.com/{tenant}/saml2
okta-samlOkta (SAML)https://{domain}.okta.com/app/{appId}/sso/saml
onelogin-samlOneLogin (SAML)https://{domain}.onelogin.com/trust/saml2/http-post/sso/{appId}
adfs-samlAD FShttps://{adfs-server}/adfs/ls
google-samlGoogle Workspace (SAML)https://accounts.google.com/o/saml2/idp?idpid={idpId}

You can retrieve the full list of presets (including attribute mappings) from the API:

Terminal window
curl https://breeze.yourdomain.com/api/v1/sso/presets \
-H "Authorization: Bearer $TOKEN"

  1. Register Breeze in your IdP. Create an application/client in your identity provider. Set the redirect URI to https://breeze.yourdomain.com/api/v1/sso/callback. Note the Client ID and Client Secret.

  2. Create the provider in Breeze. Use the POST /api/v1/sso/providers endpoint. If you specify a preset and an issuer, Breeze will auto-discover the authorization, token, userinfo, and JWKS endpoints via the .well-known/openid-configuration document.

  3. Test the configuration. Use the POST /api/v1/sso/providers/:id/test endpoint to verify that Breeze can reach the IdP’s discovery document.

  4. Activate the provider. Set the status to active via POST /api/v1/sso/providers/:id/status.

  5. Optionally enforce SSO. Update the provider with enforceSSO: true to disable password login for the organization.

Terminal window
curl -X POST https://breeze.yourdomain.com/api/v1/sso/providers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orgId": "ORG_UUID",
"name": "Azure AD",
"type": "oidc",
"preset": "azure-ad",
"issuer": "https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0",
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET"
}'

When you provide an issuer URL and the provider type is oidc, Breeze automatically fetches the IdP’s .well-known/openid-configuration document and populates:

  • authorizationUrl — Authorization endpoint
  • tokenUrl — Token endpoint
  • userInfoUrl — UserInfo endpoint
  • jwksUrl — JSON Web Key Set URI

If discovery fails (for example, if the IdP does not support well-known configuration), a warning is logged but the provider is still created. You can manually set these URLs via the PATCH endpoint afterward.


The Breeze SSO service provides a full SAML 2.0 toolkit:

  • SP Metadata generation — Produces a standards-compliant XML metadata document for your IdP.
  • AuthnRequest building — Generates SAML authentication requests with configurable NameID format and ForceAuthn.
  • Response parsing — Decodes and parses SAML responses, extracting NameID, SessionIndex, and attributes.
  • Attribute mapping — Maps SAML attribute URIs (e.g., http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress) to Breeze user fields.

Breeze generates SP metadata with the following values:

FieldValue
Entity IDConfigurable per deployment
ACS URLhttps://breeze.yourdomain.com/api/v1/sso/saml/acs
NameID Formaturn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
WantAssertionsSignedtrue

Breeze automatically maps common SAML attribute URIs to user fields:

SAML Attribute URIBreeze Field
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddressemail
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/namename
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givennamefirstName
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surnamelastName
http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsgroups
urn:oid:0.9.2342.19200300.100.1.3email
urn:oid:2.5.4.42firstName
urn:oid:2.5.4.4lastName
urn:oid:2.16.840.1.113730.3.1.241name

The OIDC login flow follows the Authorization Code + PKCE pattern:

  1. User visits login page. The frontend calls GET /api/v1/sso/check/:orgId to determine if SSO is enabled for the organization.
  2. User clicks “Sign in with SSO”. The browser navigates to GET /api/v1/sso/login/:orgId.
  3. Breeze generates PKCE challenge and state. A cryptographic state, nonce, and PKCE codeVerifier/codeChallenge pair are generated and stored in the sso_sessions table with a 10-minute TTL.
  4. Redirect to IdP. Breeze redirects the user to the IdP’s authorization endpoint with the PKCE challenge, state, nonce, and redirect URI.
  5. User authenticates at IdP. The user signs in at the identity provider.
  6. IdP redirects back. The IdP redirects to GET /api/v1/sso/callback with an authorization code and state parameter.
  7. Breeze validates state. The state is matched against the sso_sessions table to prevent CSRF.
  8. Code exchange. Breeze exchanges the authorization code for tokens at the IdP’s token endpoint, including the PKCE codeVerifier.
  9. ID token verification. If an ID token is returned, Breeze verifies the issuer, audience, expiration, and nonce claims.
  10. User info retrieval. Breeze calls the IdP’s UserInfo endpoint to get the user’s profile.
  11. Attribute mapping. The IdP’s claims are mapped to Breeze user fields using the configured attributeMapping.
  12. Domain check. If allowedDomains is set, the user’s email domain is validated.
  13. User provisioning. If the user does not exist and autoProvision is enabled, a new Breeze user is created and assigned to the organization with the provider’s defaultRoleId.
  14. Session creation. Breeze creates a JWT token pair (access + refresh) and a session record.
  15. Redirect to app. The user is redirected to the original page with a short-lived token exchange code in the URL fragment (#ssoCode=...).
  16. Token exchange. The frontend calls POST /api/v1/sso/exchange with the code to receive the JWT tokens.
Browser Breeze API Identity Provider
│ │ │
│── GET /sso/login/:orgId ──►│ │
│ │── Generate PKCE ──► │
│◄── 302 Redirect ─│──────────────────► │
│ │ │
│──────── Authenticate ──────────────────►│
│ │ │
│◄──── Redirect with code ────────────────│
│ │ │
│── GET /sso/callback?code=...&state=... ►│
│ │── Exchange code ────►│
│ │◄── Tokens ──────────│
│ │── Get user info ───►│
│ │◄── Profile ─────────│
│ │ │
│◄── 302 /#ssoCode=... ──│ │
│ │ │
│── POST /sso/exchange ──►│ │
│◄── { accessToken, refreshToken } ──────│

FieldTypeRequiredDefaultDescription
orgIduuidDependsInferred for org-scoped usersTarget organization.
namestringYesDisplay name (1—255 characters).
typeoidc or samlYesProvider protocol.
presetstringNoPreset ID (e.g., azure-ad, okta). Pre-fills scopes and attribute mapping.
issuerurlNoIdP issuer URL. Triggers OIDC discovery when set.
clientIdstringNoOAuth 2.0 Client ID.
clientSecretstringNoOAuth 2.0 Client Secret. Encrypted at rest.
scopesstringNoFrom preset or openid profile emailSpace-separated OAuth scopes.
attributeMappingobjectNoFrom preset or { email: "email", name: "name" }Maps IdP claims to Breeze fields.
autoProvisionbooleanNotrueCreate Breeze users on first SSO login.
defaultRoleIduuidNoRole assigned to auto-provisioned users. Must be an org-scoped role in the target org.
allowedDomainsstringNoComma-separated email domains. Empty means all domains are allowed.
enforceSSObooleanNofalseDisable password login for the organization.
StatusDescription
inactiveDefault on creation. The provider is not used for login.
testingIntermediate state for validation. Not used for login.
activeThe provider is live. Users can authenticate via SSO. Only one active provider per organization is used for login.
FieldTypeRequiredDescription
emailstringYesIdP claim name that contains the user’s email address.
namestringYesIdP claim name for the user’s display name.
firstNamestringNoIdP claim name for the user’s first name. Used as fallback when name is unavailable.
lastNamestringNoIdP claim name for the user’s last name. Used with firstName to construct the display name.
groupsstringNoIdP claim name for group membership. Expected to be an array of strings.

When a user authenticates via SSO, Breeze follows this logic:

  1. Email lookup. The mapped email attribute is used to find an existing Breeze user (case-insensitive).
  2. Existing user found. The user’s SSO identity link (user_sso_identities table) is created or updated with the latest profile, tokens, and login timestamp. The user is logged in.
  3. No existing user + auto-provision disabled. The login is rejected with a “user not found” error. An administrator must create the Breeze account first.
  4. No existing user + auto-provision enabled. A new Breeze user is created with the mapped email and name. The user is added to the organization with the provider’s defaultRoleId. SSO-provisioned users have no password set.
  5. Organization membership check. The user must have an organization_users record for the provider’s organization. If not, the login is rejected with “no org access”.

Each user-provider combination creates a record in the user_sso_identities table that stores:

  • External ID (sub claim from the IdP)
  • Email from the IdP
  • Profile data (full UserInfo response)
  • Access and refresh tokens (encrypted at rest)
  • Token expiration timestamp
  • Last login timestamp

This link is updated on every SSO login, keeping the profile and tokens current.


When enforceSSO is set to true on a provider, the organization’s login page should disable password-based authentication. The GET /api/v1/sso/check/:orgId endpoint returns enforceSSO: true so the frontend can hide the password form and redirect users directly to the IdP.

// GET /api/v1/sso/check/ORG_UUID
{
"ssoEnabled": true,
"provider": {
"id": "uuid",
"name": "Azure AD",
"type": "oidc"
},
"enforceSSO": true,
"loginUrl": "/api/v1/sso/login/ORG_UUID"
}

ColumnTypeDescription
iduuidPrimary key.
org_iduuidOrganization this provider belongs to (foreign key).
namevarchar(255)Display name.
typesso_provider_type enumoidc or saml.
statussso_provider_status enumactive, inactive, or testing.
issuervarchar(500)IdP issuer URL.
client_idvarchar(255)OAuth Client ID.
client_secrettextEncrypted OAuth Client Secret.
authorization_urlvarchar(500)Authorization endpoint (auto-discovered or manual).
token_urlvarchar(500)Token endpoint.
userinfo_urlvarchar(500)UserInfo endpoint.
jwks_urlvarchar(500)JWKS URI.
scopesvarchar(500)Space-separated scopes. Default: openid profile email.
entity_idvarchar(500)SAML Entity ID.
sso_urlvarchar(500)SAML SSO URL.
certificatetextSAML signing certificate.
attribute_mappingjsonbMaps IdP claims to Breeze fields.
auto_provisionbooleanAuto-create users. Default: true.
default_role_iduuidRole for auto-provisioned users.
allowed_domainsvarchar(1000)Comma-separated allowed email domains.
enforce_ssobooleanDisable password login. Default: false.
created_byuuidUser who created the provider (foreign key).
created_attimestampCreation timestamp.
updated_attimestampLast update timestamp.
ColumnTypeDescription
iduuidPrimary key.
user_iduuidBreeze user (foreign key).
provider_iduuidSSO provider (foreign key).
external_idvarchar(255)User’s sub claim from the IdP.
emailvarchar(255)Email from the IdP.
profilejsonbFull UserInfo/SAML profile data.
access_tokentextEncrypted IdP access token.
refresh_tokentextEncrypted IdP refresh token.
token_expires_attimestampToken expiration.
last_login_attimestampLast SSO login timestamp.
created_attimestampRecord creation.
updated_attimestampLast update.
ColumnTypeDescription
iduuidPrimary key.
provider_iduuidSSO provider (foreign key).
statevarchar(64)CSRF protection state parameter (unique).
noncevarchar(64)ID token nonce.
code_verifiervarchar(128)PKCE code verifier.
redirect_urlvarchar(500)Post-login redirect path.
expires_attimestampSession expiration (10 minutes from creation).
created_attimestampCreation timestamp.

All routes are prefixed with /api/v1/sso.

MethodPathPermissionDescription
GET/sso/presetsAuthenticatedList available provider presets with their default configurations.
GET/sso/providersAuthenticatedList SSO providers for the organization. Accepts orgId query parameter.
GET/sso/providers/:idAuthenticatedGet provider details. The clientSecret is not returned; hasClientSecret indicates if one is set.
POST/sso/providersorganizations:write + MFACreate a new SSO provider. Returns with status: inactive.
PATCH/sso/providers/:idorganizations:write + MFAUpdate provider configuration.
DELETE/sso/providers/:idorganizations:write + MFADelete a provider and all associated sessions and identity links.
POST/sso/providers/:id/statusorganizations:write + MFASet provider status (active, inactive, testing).
POST/sso/providers/:id/testorganizations:write + MFATest OIDC provider configuration by running discovery.
MethodPathAuthDescription
GET/sso/check/:orgIdNoneCheck if SSO is enabled for an organization. Returns provider name, type, enforcement status, and login URL.
GET/sso/login/:orgIdNoneInitiate SSO login. Generates PKCE + state and redirects to the IdP.
GET/sso/callbackNoneIdP callback. Exchanges code for tokens, provisions user, creates session. Redirects to the app with a token exchange code.
POST/sso/exchangeNoneExchange a one-time SSO code for JWT access and refresh tokens.

SSO operations are recorded in the audit log:

ActionTrigger
sso.provider.createProvider created. Includes type and status.
sso.provider.updateProvider configuration changed. Includes list of changed fields.
sso.provider.deleteProvider deleted.
sso.provider.status.updateProvider status changed (active/inactive/testing).
sso.provider.testProvider configuration tested.

VariableRequiredDescription
PUBLIC_URL or PUBLIC_APP_URL or DASHBOARD_URLYes (one of)Base URL of the Breeze dashboard. Used to construct the SSO callback URI ({base}/api/v1/sso/callback). Falls back to http://localhost:3000.
SECRET_ENCRYPTION_KEY or APP_ENCRYPTION_KEYYesUsed to encrypt/decrypt the clientSecret at rest.

”No active SSO provider for this organization” (404)

Section titled “”No active SSO provider for this organization” (404)”

The GET /sso/login/:orgId endpoint could not find a provider with status: active for the given organization. Verify that:

  • A provider exists for the organization.
  • The provider status has been set to active via POST /sso/providers/:id/status.

The provider is missing one or more of clientId, clientSecret, or issuer. Update the provider via PATCH /sso/providers/:id with the missing values.

Breeze could not fetch the .well-known/openid-configuration document from the issuer URL. Check that:

  • The issuer URL is correct and reachable from the Breeze API server.
  • The IdP’s discovery endpoint returns a valid JSON document.
  • No firewall or proxy is blocking the connection.

The SSO session (state parameter) has expired. SSO sessions are valid for 10 minutes. The user should retry the login flow. If this happens frequently, check for clock skew between the Breeze server and the IdP.

The user’s email domain is not in the provider’s allowedDomains list. Update the provider to include the domain, or remove the allowedDomains restriction.

The user does not exist in Breeze and autoProvision is disabled on the provider. Either:

  • Enable auto-provisioning on the provider (autoProvision: true).
  • Create the user account manually before they attempt SSO login.

Auto-provisioning is enabled but no defaultRoleId is configured on the provider, or the configured role does not exist as an organization-scoped role. Set a valid defaultRoleId via PATCH /sso/providers/:id.

The user exists in Breeze but does not have an organization_users record for the provider’s organization. Add the user to the organization or enable auto-provisioning with a defaultRoleId.

”Invalid or expired token exchange code” (400)

Section titled “”Invalid or expired token exchange code” (400)”

The one-time SSO code passed to POST /sso/exchange has either:

  • Already been consumed (codes are single-use).
  • Expired (codes are valid for 2 minutes).
  • Been corrupted during the redirect.

The user should retry the SSO login flow.

SSO callback redirects to /login?error=sso_error

Section titled “SSO callback redirects to /login?error=sso_error”

An unhandled error occurred during the callback. Check the Breeze API server logs for the full error message. Common causes include:

  • Network connectivity issues between Breeze and the IdP’s token endpoint.
  • Mismatched clientSecret (the secret in the IdP does not match what was configured in Breeze).
  • ID token verification failure (wrong issuer, audience, or expired token).
  • Nonce mismatch (indicates a replay attack or stale session).