Last updated

edX SSO Setup (Google, Microsoft, Apple, OpenID Connect)

Purpose

This document explains how to add Single Sign-On (SSO) identity providers to the Open edX LMS (iblai-edx-pro) so that learners can log in with an external identity provider (IdP) instead of a local edX password.

It covers the four backend families we support:

ProvideredX backend name (slug)Backend class
Googlegoogle-oauth2social_core.backends.google.GoogleOAuth2
Microsoft (Azure AD / Entra ID)azuread-oauth2social_core.backends.azuread.AzureADOAuth2
Appleapple-idsocial_core.backends.apple.AppleIdAuth
OpenID Connect (generic OIDC)ibl-oauth2 (configurable)ibl_base_oauth_sso_backend.auth.IBLBaseOAuthSSOBackend

Google, Microsoft, and Apple use the stock social-core backends that ship with edX. OpenID Connect uses the in-house ibl-edx-base-oauth-sso-backend-app plugin, which wraps social_core.backends.open_id_connect.OpenIdConnectAuth and can talk to any standards-compliant OIDC provider (Okta, Ping, ForgeRock/AM, Auth0, Keycloak, a corporate IdP, etc.).

There are two layers of configuration for every provider:

  1. Deployment config (ibl config save ...) - enables third-party auth, registers the backend classes, and (for OIDC) sets the endpoint/claim settings baked into the LMS Django settings by a Tutor plugin.
  2. Runtime config (LMS Django admin at /admin/third_party_auth/oauth2providerconfig/) - one row per provider that carries the client ID, client secret, and per-provider options. This can be changed without a redeploy.

Throughout this guide the example tenant is iblai and the example LMS domain is learn.iblai.com. Substitute your own platform key and domain.


How it fits together

Learner -> LMS login page -> /auth/login// -> IdP consent
       -> IdP redirects to /auth/complete// -> edX third_party_auth
       -> social-core backend validates token/claims
       -> edX finds-or-creates the user, links UserSocialAuth
       -> (OIDC only) user is linked to the platform_key tenant
  • ENABLE_THIRD_PARTY_AUTH turns the edX third-party-auth subsystem on.
  • THIRD_PARTY_AUTH_BACKENDS is the allowlist of backend classes edX may load.
  • Each enabled provider needs a matching OAuth2ProviderConfig row whose Backend name equals the backend's slug.

The deployment config lives in iblai-cli-ops/ibl/templates/config/defaults.yml under IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND, and is rendered into LMS settings by the Tutor plugin iblai-cli-ops/ibl/templates/tutor-plugins/ibl-edx-base-oauth-sso-backend.py.

Important: the plugin gate

The entire plugin (including the ENABLE_THIRD_PARTY_AUTH and THIRD_PARTY_AUTH_BACKENDS patches) is wrapped in:

{% if IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.ENABLE_EDX_BASE_OAUTH_SSO_BACKEND %}
...
{% endif %}

ENABLE_EDX_BASE_OAUTH_SSO_BACKEND defaults to false. That means the third-party-auth settings this plugin manages are only applied when the plugin is enabled. You must enable it even if you only intend to use the stock Google / Microsoft / Apple backends, because that flag also gates the ENABLE_THIRD_PARTY_AUTH: true and THIRD_PARTY_AUTH_BACKENDS patches.


Step 0: Prerequisites (per provider)

Before touching edX, register an OAuth2 / OIDC client with the IdP and collect:

  • Client ID and Client Secret.

  • The exact redirect URI (also called callback / reply URL), which must be:

    https://learn.iblai.com/auth/complete//

    For example:

    • Google: https://learn.iblai.com/auth/complete/google-oauth2/
    • Microsoft: https://learn.iblai.com/auth/complete/azuread-oauth2/
    • Apple: https://learn.iblai.com/auth/complete/apple-id/
    • OIDC: https://learn.iblai.com/auth/complete/iblai-oauth2/ (your chosen slug)
  • Scopes: request at least openid profile email so edX can build a username, email, and full name.

  • For OIDC only: the provider's discovery URL (.../.well-known/openid-configuration). The value before that suffix is your OIDC_ENDPOINT. Example: if discovery is https://id.iblai.com/oauth2/realms/alpha/.well-known/openid-configuration, then OIDC_ENDPOINT = https://id.iblai.com/oauth2/realms/alpha.


Step 1: Enable third-party auth (deployment config)

Run these once per environment. Each ibl config save --set call writes a single KEY=VALUE override into the user config layered on top of defaults.yml.

# Required: activates the plugin block (and therefore the two patches below)
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.ENABLE_EDX_BASE_OAUTH_SSO_BACKEND=true

# Turn the edX third-party-auth subsystem on
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.ENABLE_THIRD_PARTY_AUTH=true

THIRD_PARTY_AUTH_BACKENDS already includes Google, Microsoft (AzureAD), Apple, and the IBL OIDC backends in defaults.yml:

THIRD_PARTY_AUTH_BACKENDS:
  - social_core.backends.google.GoogleOAuth2
  - social_core.backends.azuread.AzureADOAuth2
  - social_core.backends.apple.AppleIdAuth
  - ibl_base_oauth_sso_backend.auth.IBLBaseOAuthSSOBackend
  - ibl_base_oauth_sso_backend.auth_openid.IBLOpenIDOAuthSSOBackend
  # ... other backends

Only override it if you want to trim the list to the exact set you use. It must be passed as a JSON array string:

ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.THIRD_PARTY_AUTH_BACKENDS='["social_core.backends.google.GoogleOAuth2", "social_core.backends.azuread.AzureADOAuth2", "social_core.backends.apple.AppleIdAuth", "ibl_base_oauth_sso_backend.auth.IBLBaseOAuthSSOBackend"]'

Apply the config (see Step 4) after this step.


Step 2: Provider-specific deployment config

Google

No plugin settings are required. Google is a stock backend; the client ID and secret are entered in the Django admin (Step 3). Ensure social_core.backends.google.GoogleOAuth2 is in THIRD_PARTY_AUTH_BACKENDS (it is by default).

Microsoft (Azure AD / Entra ID)

No plugin settings are required for a single-tenant or multi-tenant Azure AD app; the client ID and secret are entered in the Django admin (Step 3). Ensure social_core.backends.azuread.AzureADOAuth2 is in THIRD_PARTY_AUTH_BACKENDS (it is by default).

Notes:

  • The Azure AD "Application (client) ID" is the Client ID; a client secret value (not the secret ID) is the Client Secret.
  • azuread-oauth2 is TRACKED_PROVIDERS by default, so an Azure AD user is linked to the tenant named in the provider's other_settings.platform_key (see Linking users to a tenant).

Apple

No plugin settings are required, but Apple's credentials are shaped differently from Google/Microsoft:

  • Client ID is the Apple Services ID (for example com.iblai.learn.signin).
  • Client Secret is a signed JWT that Apple requires you to generate from your .p8 private key, Team ID, and Key ID. It expires (max 6 months), so it must be regenerated periodically or generated dynamically.
  • Ensure social_core.backends.apple.AppleIdAuth is in THIRD_PARTY_AUTH_BACKENDS (it is by default).
  • Apple only returns the user's name on the first authorization, so treat the email claim as the durable identifier.

OpenID Connect (generic OIDC via the IBL backend)

This is the backend defined in ibl_base_oauth_sso_backend/auth.py (IBLBaseOAuthSSOBackend). It reads its settings from the IBL_OAUTH_SSO_* namespace. Set the endpoint, name (slug), and claim mappings via ibl config save.

Minimum (provider exposes a discovery document):

# The backend slug / provider name. This becomes the login+callback slug.
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_NAME='iblai-oauth2'

# The OIDC issuer base (everything before /.well-known/openid-configuration)
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_OIDC_ENDPOINT='https://id.iblai.com/oauth2/realms/alpha'

Claim -> edX field mappings (defaults shown; override only if your IdP uses different claim names):

ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_USERNAME_KEY='preferred_username'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_EMAIL_KEY='email'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_FIRST_NAME_KEY='given_name'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_LAST_NAME_KEY='family_name'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_FULLNAME_KEY='name'

Optional endpoints - set these only if the provider has no discovery document, or you need to pin them. When IBL_OAUTH_SSO_OIDC_ENDPOINT is set, the backend fetches these from /.well-known/openid-configuration automatically.

ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_AUTHORIZATION_URL='https://id.iblai.com/oauth2/authorize'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_ACCESS_TOKEN_URL='https://id.iblai.com/oauth2/token'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_USERINFO_URL='https://id.iblai.com/oauth2/userinfo'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_JWKS_URI='https://id.iblai.com/oauth2/connect/jwk_uri'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_ID_TOKEN_ISSUER='https://id.iblai.com/oauth2'

Common OIDC options:

# Some IdPs (for example ForgeRock/AM) send an nbf claim the default validator rejects.
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_JWT_DECODE_OPTIONS='{ "verify_nbf": False }'

# Require HTTPS on the callback (recommended in production)
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.SOCIAL_AUTH_REDIRECT_IS_HTTPS=true
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.SSL_PROTOCOL=true

# Only allow existing users to log in (block auto-provisioning of new accounts)
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_DISABLE_USER_CREATION=true

# Read user fields from the id_token instead of the userinfo response
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_USE_IDTOKEN_FIELDS=true
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OAUTH_SSO_IDTOKEN_FIELD_MAPPINGS='{ "username": "preferred_username", "email": "email", "first_name": "given_name", "last_name": "family_name", "fullname": "name" }'
Running more than one OIDC provider

edX keys each OAuth2ProviderConfig by (site_id, backend_name). A second config row for the same backend name supersedes the first, it does not run both. To run two OIDC providers side by side, use the second backend class, which reads a parallel IBL_OPENID_SSO_* namespace and defaults to the openid-oauth2 slug:

ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OPENID_SSO_NAME='partner-oauth2'
ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.IBL_OPENID_SSO_OIDC_ENDPOINT='https://idp.partner.com/oauth2'

IBLOpenIDOAuthSSOBackend (openid-oauth2) and IBLOpenIDTwoBackend (openidtwo-oauth2) are already in the default THIRD_PARTY_AUTH_BACKENDS. Each gets its own admin config row.


Step 3: Add the provider in the LMS Django admin

After the config is applied and the LMS has restarted, add one provider config at:

https://learn.iblai.com/admin/third_party_auth/oauth2providerconfig/

Click Add OAuth2 Provider Config. Each save creates a new versioned row; the newest enabled row for a backend name wins.

Common fields (all four providers)

FieldValueNotes
EnabledcheckedActivates the provider.
Namee.g. Google, Microsoft, Apple, IBL AI SSODisplay name shown on the login page.
Slugthe backend sluggoogle-oauth2, azuread-oauth2, apple-id, or your OIDC slug.
Sitethe LMS site (e.g. learn.iblai.com)Must match the site users log in on.
Backend namesame slug as aboveMust equal the backend's name.
Client IDfrom the IdPSee per-provider notes below.
Client Secretfrom the IdPSee per-provider notes below.
VisiblecheckedShow the button on the login/register page.
Skip hinted login dialogcheckedSmoother SSO-first experience.
Skip registration formcheckedAuto-provision from claims, no extra form.
Skip email verificationcheckedTrust the IdP-verified email.
Sync learner profile datacheckedKeep name/email in sync on each login.

Per-provider values for the admin row

  • Google - Backend name google-oauth2; Client ID and Client Secret from the Google Cloud OAuth client.
  • Microsoft - Backend name azuread-oauth2; Client ID = Azure "Application (client) ID"; Client Secret = the secret value.
  • Apple - Backend name apple-id; Client ID = Apple Services ID; Client Secret = the signed JWT (see Apple).
  • OpenID Connect - Backend name = your IBL_OAUTH_SSO_NAME slug (for example iblai-oauth2); Client ID and Client Secret from the OIDC client.

Other settings (JSON)

The Other settings field takes a JSON object. social-core overlays these on top of the namespace defaults, so you can also pin per-provider endpoints here without a redeploy.

For the IBL OIDC backend the useful keys are:

{
  "platform_key": "iblai",
  "OIDC_ENDPOINT": "https://id.iblai.com/oauth2/realms/alpha",
  "logout_url": "https://id.iblai.com/oauth2/logout"
}
  • platform_key - the tenant/platform this provider's users are linked to (see below).
  • OIDC_ENDPOINT - optional override of the discovery base for this row.
  • logout_url - optional; used by the backend's single-logout flow. If omitted, the backend uses the provider's end_session_endpoint from discovery.

Linking users to a tenant (TRACKED_PROVIDERS)

When a provider's slug is in TRACKED_PROVIDERS, users who log in through it are automatically linked to the tenant named in that provider's other_settings.platform_key. The default tracked list is:

azuread-oauth2, cloud-ibl-sso, atom-ibl-sso, atom-sp-ibl-sso,
vhl-oauth2, openid-oauth2, openidtwo-oauth2

If you want Google/Apple or a custom OIDC slug to auto-link to a tenant, add its slug to TRACKED_PROVIDERS and set platform_key in its other_settings:

ibl config save --set IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND.TRACKED_PROVIDERS='["azuread-oauth2", "google-oauth2", "iblai-oauth2"]'

A tracked provider with no platform_key in other_settings is skipped (the user is authenticated but not linked to a tenant).


Step 4: Apply and restart

Deployment-config changes (Steps 1-2) only take effect after the config is regenerated and the LMS is restarted:

ibl config save            # regenerate compose/settings from the user config
ibl tutor config save      # render the Tutor plugin into LMS settings
ibl edx stop
ibl edx start -d

Django-admin changes (Step 3) take effect immediately, no restart required.


Verify

  1. Open https://learn.iblai.com/login. The enabled provider buttons should appear.
  2. Click a provider, complete the IdP login, and confirm you land back on the LMS authenticated.
  3. In the admin, check /admin/social_django/usersocialauth/ for a new row with the expected provider (slug) and uid.
  4. For OIDC tenant linking, confirm the user appears under the expected platform_key tenant.

Troubleshooting

  • Provider button not shown - The plugin gate (ENABLE_EDX_BASE_OAUTH_SSO_BACKEND=true) or ENABLE_THIRD_PARTY_AUTH is off, the config was not re-applied/restarted, or the admin row is not Enabled / Visible.
  • Backend not found / not loaded - The backend class is missing from THIRD_PARTY_AUTH_BACKENDS, or the admin Backend name does not exactly match the backend slug.
  • redirect_uri_mismatch - The redirect URI registered with the IdP does not exactly match https://learn.iblai.com/auth/complete// (check trailing slash and http vs https).
  • State validation failures (intermittent) - The IBL OIDC backend stores OAuth state in Redis with a session fallback specifically to survive concurrent logout and load-balancer session loss; if you still see state errors, check that Redis is reachable from the LMS.
  • Email is required but not provided - The IdP did not return an email claim. Ensure the email scope is granted and IBL_OAUTH_SSO_EMAIL_KEY matches the claim name.
  • New users blocked - IBL_OAUTH_SSO_DISABLE_USER_CREATION=true only allows pre-existing users to log in.
  • OIDC token rejected on nbf - Set IBL_OAUTH_SSO_JWT_DECODE_OPTIONS='{ "verify_nbf": False }'.

Reference

  • Plugin template: iblai-cli-ops/ibl/templates/tutor-plugins/ibl-edx-base-oauth-sso-backend.py
  • Defaults: iblai-cli-ops/ibl/templates/config/defaults.yml (IBL_EDX.IBL_EDX_BASE_OAUTH_SSO_BACKEND)
  • OIDC backend: iblai-edx-pro/apps/ibl-edx-base-oauth-sso-backend-app/src/ibl_base_oauth_sso_backend/auth.py
  • Setting definitions and defaults: .../ibl_base_oauth_sso_backend/config.py
  • Tenant-linking signal: .../ibl_base_oauth_sso_backend/signals.py

Copyright © ibl.ai | support@iblai.zendesk.com