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:
| Provider | edX backend name (slug) | Backend class |
|---|---|---|
google-oauth2 | social_core.backends.google.GoogleOAuth2 | |
| Microsoft (Azure AD / Entra ID) | azuread-oauth2 | social_core.backends.azuread.AzureADOAuth2 |
| Apple | apple-id | social_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:
- 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. - 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_AUTHturns the edX third-party-auth subsystem on.THIRD_PARTY_AUTH_BACKENDSis the allowlist of backend classes edX may load.- Each enabled provider needs a matching
OAuth2ProviderConfigrow 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)
- Google:
Scopes: request at least
openid profile emailso 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 yourOIDC_ENDPOINT. Example: if discovery ishttps://id.iblai.com/oauth2/realms/alpha/.well-known/openid-configuration, thenOIDC_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
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-oauth2isTRACKED_PROVIDERSby default, so an Azure AD user is linked to the tenant named in the provider'sother_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
.p8private 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.AppleIdAuthis inTHIRD_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)
| Field | Value | Notes |
|---|---|---|
| Enabled | checked | Activates the provider. |
| Name | e.g. Google, Microsoft, Apple, IBL AI SSO | Display name shown on the login page. |
| Slug | the backend slug | google-oauth2, azuread-oauth2, apple-id, or your OIDC slug. |
| Site | the LMS site (e.g. learn.iblai.com) | Must match the site users log in on. |
| Backend name | same slug as above | Must equal the backend's name. |
| Client ID | from the IdP | See per-provider notes below. |
| Client Secret | from the IdP | See per-provider notes below. |
| Visible | checked | Show the button on the login/register page. |
| Skip hinted login dialog | checked | Smoother SSO-first experience. |
| Skip registration form | checked | Auto-provision from claims, no extra form. |
| Skip email verification | checked | Trust the IdP-verified email. |
| Sync learner profile data | checked | Keep 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_NAMEslug (for exampleiblai-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'send_session_endpointfrom 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
- Open
https://learn.iblai.com/login. The enabled provider buttons should appear. - Click a provider, complete the IdP login, and confirm you land back on the LMS authenticated.
- In the admin, check
/admin/social_django/usersocialauth/for a new row with the expectedprovider(slug) anduid. - For OIDC tenant linking, confirm the user appears under the expected
platform_keytenant.
Troubleshooting
- Provider button not shown - The plugin gate
(
ENABLE_EDX_BASE_OAUTH_SSO_BACKEND=true) orENABLE_THIRD_PARTY_AUTHis off, the config was not re-applied/restarted, or the admin row is notEnabled/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
emailscope is granted andIBL_OAUTH_SSO_EMAIL_KEYmatches the claim name. - New users blocked -
IBL_OAUTH_SSO_DISABLE_USER_CREATION=trueonly 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