Skip to content

SSO Setup Guide

PRO + ENTERPRISE Feature

SSO configuration requires a PRO or ENTERPRISE tier licence.

Configure Single Sign-On (SSO) to allow users to authenticate using your organisation's identity provider.


Overview

CertifyClouds PRO supports SSO via OpenID Connect (OIDC):

Provider Type Description Use When
azure_ad Microsoft Entra ID (first-class support) Your organisation uses Microsoft 365 or Microsoft Entra ID
oidc Generic OIDC Using Okta, Auth0, Keycloak, PingFederate, or other OIDC providers

SAML Support

SAML 2.0 is on the roadmap and not yet shipped. If your IdP only supports SAML, contact sales to discuss your requirements. Most SAML-capable IdPs (Microsoft Entra ID, Okta, Auth0, Keycloak, PingFederate) also support OIDC, which this guide covers.


Prerequisites

Before configuring SSO:

  1. PRO tier licence - SSO is not available in STARTER tier
  2. Admin access to your Identity Provider (IdP)
  3. CertifyClouds URL - Your deployment's base URL

Required Information

Item Description Example
CertifyClouds URL Your deployment's base URL https://certifyclouds.yourcompany.com
Callback URL SSO callback endpoint https://certifyclouds.yourcompany.com/api/auth/sso/callback
Client ID From your IdP app registration 12345678-abcd-...
Client Secret From your IdP app registration secret_...
Issuer URL OIDC discovery endpoint base https://login.microsoftonline.com/{tenant}/v2.0

Multiple Environments (Important!)

If you access CertifyClouds from different URLs (localhost, ACI, CAE, custom domain), you must register all redirect URIs in your IdP:

Environment Redirect URI
Azure Container Instances http://<ACI_PRIVATE_IP>:8080/api/auth/sso/callback
Container Apps (internal) https://<CAE_FQDN>/api/auth/sso/callback
Custom domain https://certifyclouds.yourcompany.com/api/auth/sso/callback

Why is this needed?

OAuth 2.0 requires the redirect URI to exactly match one registered in the App Registration. CertifyClouds dynamically detects which URL you're accessing from, but your IdP must allow all of them. If you set App base URL in Settings → Advanced → Network (or APP_BASE_URL on the deployment), SSO uses that address instead of detecting it.


Microsoft Entra ID Setup

Step 1: Create App Registration

  1. Go to Azure Portal → Microsoft Entra ID → App registrations
  2. Click + New registration
  3. Configure:
  4. Name: CertifyClouds SSO Client (Production)
  5. Supported account types: Accounts in this organizational directory only
  6. Redirect URI:
    • Platform: Web
    • URI: https://YOUR_CERTIFYCLOUDS_URL/api/auth/sso/callback
  7. Click Register

Step 2: Configure Authentication

  1. In your app registration, go to Authentication
  2. Under Implicit grant and hybrid flows, ensure:
  3. [ ] Access tokens - Unchecked (we use authorization code flow)
  4. [ ] ID tokens - Unchecked
  5. Under Advanced settings:
  6. Allow public client flows: No

Step 3: Create Client Secret

  1. Go to Certificates & secrets → Client secrets
  2. Click + New client secret
  3. Configure:
  4. Description: CertifyClouds SSO Client Credential
  5. Expires: 24 months (recommended)
  6. Click Add
  7. Copy the secret Value immediately. It is not shown again.

Step 4: Configure API Permissions

  1. Go to API permissions
  2. Click + Add a permission → Microsoft Graph → Delegated permissions
  3. Add these permissions:
  4. openid (Sign users in)
  5. profile (View users' basic profile)
  6. email (View users' email address)
  7. User.Read (Sign in and read user profile)
  8. GroupMember.Read.All (Required for group-based role mapping)
  9. Click Grant admin consent for [Your Organization]
  10. Verify all permissions show green checkmarks

Upgrade from < 1.4.12 - required if you use group→role mapping

Before 1.4.12 CertifyClouds requested only openid profile email User.Read in the SSO OAuth flow. With just User.Read, Microsoft Graph returns the user's group memberships but redacts each group's displayName to null, so the group→role mapping silently skipped every sign-in.

CertifyClouds 1.4.12 only requests GroupMember.Read.All when a groupRoleMapping is configured. Behaviour for SSO customers without mapping is unchanged - no fresh consent prompt, no AADSTS65001 errors on upgrade.

If your SSO config DOES have a groupRoleMapping (or you plan to configure one), you must:

  1. Add GroupMember.Read.All to the app registration's API permissions (Step 4 above)
  2. Click Grant admin consent for [Your Organization]

Without this, the next SSO sign-in after upgrading to 1.4.12 will fail with AADSTS65001: The user or administrator has not consented to use the application... until consent is granted.

Verify the fix worked by inspecting the Audit Log for auth.sso.role_changed entries after a user logs in.

Mapping keys are matched on the Entra group object ID (the GUID), never on the display name. A display name is not unique and anyone who can create or rename a group could otherwise claim a mapped role. When you save a mapping whose key is a display name, CertifyClouds resolves it to the object ID at save time and stores the ID (the name is kept only as a label). That lookup uses the CertifyClouds platform identity and needs GroupMember.Read.All (application permission) on it; without that permission, paste the group object ID directly and the save succeeds with no lookup. Mappings saved by versions before 1.8.0 that still hold display-name keys keep working, with a warning in the logs and a banner flag until you re-save them.

Role changes are only applied when the group lookup at sign-in completed successfully: a user whose mapped group is removed is moved to the default role, but a failed or truncated lookup never demotes anyone, and the last active administrator is never demoted by SSO.

Step 5: Collect Configuration Values

Setting Where to Find
Client ID App registration → Overview → Application (client) ID
Client Secret The value you copied in Step 3
Tenant ID App registration → Overview → Directory (tenant) ID
Issuer URL https://login.microsoftonline.com/{TENANT_ID}/v2.0

Okta Setup

Step 1: Create Application

  1. Go to Okta Admin Console → Applications → Applications
  2. Click Create App Integration
  3. Select:
  4. Sign-in method: OIDC - OpenID Connect
  5. Application type: Web Application
  6. Click Next

Step 2: Configure Application

  1. App integration name: CertifyClouds
  2. Grant type:
  3. [x] Authorization Code
  4. [ ] Implicit (unchecked)
  5. Sign-in redirect URIs:
  6. https://YOUR_CERTIFYCLOUDS_URL/api/auth/sso/callback
  7. Controlled access: Select appropriate option
  8. Click Save

Step 3: Collect Values

Setting Where to Find
Client ID General tab → Client Credentials
Client Secret General tab → Client Credentials
Issuer URL https://yourcompany.okta.com

Step 4: Assign Users

  1. Go to Assignments tab
  2. Click Assign → Assign to People or Assign to Groups
  3. Select users/groups who should access CertifyClouds

Step 5 (Optional): Emit Groups in the ID Token

Required only if you want Group-Based Role Mapping to assign admin/user roles automatically from Okta group memberships.

  1. Okta Admin Console → Security → API → Authorization Servers
  2. Select the authorization server CertifyClouds uses
  3. Claims tab → + Add Claim
  4. Configure:
  5. Name: groups
  6. Include in token type: ID Token, Always
  7. Value type: Groups
  8. Filter: Matches regex / .* (or scope to specific groups)
  9. Save

Now Okta will include a groups claim in every ID token listing the user's group memberships as strings. CertifyClouds reads this claim (name configurable via the groupsClaim field - defaults to groups) and maps it against your groupRoleMapping on every sign-in.


Generic OIDC Provider

For other OIDC providers (Auth0, Keycloak, PingFederate, Google Workspace, etc.):

Prerequisites

Your OIDC provider must support:

  • Authorization Code flow
  • Standard OIDC claims: sub, email, name
  • OIDC Discovery endpoint (/.well-known/openid-configuration)

Configuration

CertifyClouds Setting Your IdP Value
Provider Type oidc
Issuer URL Base URL for OIDC discovery
Client ID From your IdP
Client Secret From your IdP
Scopes openid profile email
Groups claim (optional) Name of the ID-token claim that lists group memberships. Default: groups. Set to whatever your IdP emits (Auth0 default rule: https://YOUR_TENANT/groups; Keycloak default: groups; Google Workspace: roll your own via a custom claim).

Group-Based Role Mapping for Generic OIDC

Unlike Microsoft Entra ID (which CertifyClouds queries via Microsoft Graph), generic OIDC providers deliver group memberships inside the ID token under a claim whose name varies by provider. CertifyClouds supports three claim shapes - most providers fit one without any configuration:

Shape Example Used by
Flat list of strings {"groups": ["Engineering", "Admins"]} Okta default, Keycloak default
Flat list of objects {"groups": [{"name": "Engineering"}, {"name": "Admins"}]} Auth0 custom rules
Single string {"groups": "Engineering"} Some on-prem OIDC servers

CertifyClouds extracts the names (and, for object form, also the IDs) and matches them against groupRoleMapping keys - first match wins. If your IdP doesn't emit a groups claim by default, every IdP documents how to add one (Okta: see Step 5 above; Auth0: Rules / Actions adding context.user.groups to the ID token; Keycloak: Client Scopes → groups mapper → "Add to ID token").


CertifyClouds Configuration

Step 1: Access SSO Settings

  1. Sign in to CertifyClouds as admin
  2. Go to Settings → SSO
  3. Open the Provider Configuration section and pick your provider

Step 2: Enter Configuration

Field Description
Provider Type Select Microsoft Entra ID or OpenID Connect
Issuer URL Your IdP's OIDC issuer URL
Client ID Application/Client ID from your IdP
Client Secret Secret from your IdP (encrypted at rest)
Sign-in button text Custom text (e.g., "Sign in with Company SSO")

Step 3: Configure User Provisioning

Option Description
Auto-create users Automatically create accounts for SSO users
Default role Role assigned to auto-created users
SSO-only mode Disable local username/password sign-in

Step 4: Test and Save

  1. Click Test Connection to verify IdP connectivity
  2. If successful, click Save Configuration
  3. Test sign-in in a private browser window

Group-Based Role Mapping

Automatically assign roles based on Entra group membership. When configured, CertifyClouds checks the user's groups on every sign-in and updates their role accordingly.

Prerequisites

Your Entra app registration must have GroupMember.Read.All (delegated) with admin consent granted - see Step 4 above - so CertifyClouds can read the signing-in user's group memberships. If your org cannot grant that scope, group→role mapping cannot see the user's groups and will leave roles unchanged.

How It Works

On every sign-in CertifyClouds reads the user's Entra group object IDs via Microsoft Graph and matches them against the mapping keys. The first matching group wins. Matching is by object ID only: display names are not unique, and anyone who can create or rename a group could otherwise claim a mapped role.

What happens when no mapping matches depends on whether the lookup was trustworthy:

  • Lookup succeeded (all pages read, no error) and no group matched → the user is moved to the default role. Removing someone from the admin group therefore removes their admin access on their next sign-in.
  • Lookup failed or was truncated → the user keeps their current role. A transient Entra outage never demotes anyone.
  • The last active administrator is never demoted by SSO mapping, so a mis-configured mapping cannot lock the tenant out.

Role changes invalidate the user's other sessions and are recorded in the Audit Log as auth.sso.role_changed.

Mapping keys

Use the Entra group's object ID (the GUID from Entra → Groups → your group → Overview):

"groupRoleMapping": {
  "<admin-group-object-id>": "admin",
  "<user-group-object-id>": "user"
}

You may type a display name in the mapping editor for convenience: on save, CertifyClouds resolves it to the group's object ID and stores the ID, keeping the name only as a label. That save-time lookup runs under the CertifyClouds platform identity and needs GroupMember.Read.All (application permission) on it; if the name cannot be resolved the save is rejected with a message naming the group - paste the object ID instead.

Mappings created before 1.8.0 that still contain display-name keys keep matching by name, with a warning logged at each sign-in and a flag on the SSO configuration until you re-save them; re-save to migrate them to object IDs.

Configure via API or UI

The Group→Role mapping editor is available in the SSO settings UI from 1.4.12 (Settings → SSO → IdP config form). Or configure it via the API:

Step 1 - Get a token:

TOKEN=$(curl -s -X POST http://YOUR_INSTANCE/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YOUR_PASSWORD"}' \
  | jq -r '.access_token')

Step 2 - Read your current SSO config:

curl -s http://YOUR_INSTANCE/api/auth/sso/config \
  -H "Authorization: Bearer $TOKEN" | jq

Step 3 - PUT it back with groupRoleMapping added:

curl -X PUT http://YOUR_INSTANCE/api/auth/sso/config \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "providerType": "azure_ad",
    "azureTenantId": "YOUR_TENANT_ID",
    "oidcClientId": "YOUR_CLIENT_ID",
    "isEnabled": true,
    "autoCreateUsers": true,
    "defaultRole": "user",
    "groupRoleMapping": {
      "CC-Admins": "admin",
      "CC-Users": "user"
    }
  }'

Replace the group names with your actual Entra group display names (case-sensitive). Valid roles are admin and user.

Full replace

The PUT endpoint replaces the entire config. Copy all fields from your GET response before submitting - any field you omit reverts to its default.


Troubleshooting

Common Issues

Issue Cause Solution
"Invalid redirect URI" Callback URL mismatch Ensure IdP redirect URI exactly matches CertifyClouds callback URL
"Redirect to localhost" Missing redirect URI for environment Add all environment URLs (ACI IP, CAE FQDN, custom domain) to your IdP's redirect URIs
"Invalid client_id" Wrong Client ID Double-check Client ID in settings
"AADSTS50011" Reply URL not registered Add callback URL to the Entra app registration. Ensure you've added ALL environment URLs
"AADSTS700016" App not found in tenant Verify app registration is in the correct tenant
"User not authorized" User not assigned to app Assign user/group in IdP

Debug Tips

  1. Enable SSO debug logging: LOG_SSO_DEBUG=true
  2. Test OIDC discovery:
    curl https://YOUR_ISSUER_URL/.well-known/openid-configuration | jq
    
  3. Check for required endpoints: authorization_endpoint, token_endpoint, jwks_uri

Security Best Practices

  1. Use short-lived client secrets - Rotate every 12-24 months
  2. Limit application permissions - Only request necessary scopes
  3. Enable SSO-only mode - After testing, disable local password sign-in
  4. Use conditional access - Configure MFA via your IdP
  5. Monitor SSO logins - Review audit logs for unusual activity