Entra SSO for WordPress
OpenID Connect SSO for WordPress with Microsoft Entra ID: MFA support, automatic user provisioning, and security-group-to-role mapping
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/redrofigt/wp-entra-sso/archive/refs/heads/main.zipReadme
Entra SSO for WordPress
OpenID Connect single sign-on for WordPress with Microsoft Entra ID: MFA-aware login, automatic user provisioning, and security-group → WordPress-role mapping that re-syncs on every login.
No Composer dependencies — a deliberate choice for enterprise WordPress environments where the dependency footprint must be auditable.
The flow this implements (matches the screening-question recording)
Browser WordPress Microsoft Entra ID
│ 1. click "Sign in" │ │
│ ───────────────────────▶│ │
│ │ 2. 302 /authorize │
│ ◀───────────────────────┤ (state + nonce + PKCE) │
│ 3. login + MFA ─────────────────────────────────────▶│
│ ◀──────────────────────────────────── code + state ───┤
│ ───────────────────────▶│ 4. validate state (single-use,
│ │ TTL 5 min), exchange code │
│ │ ─────────────────────────────▶│
│ │ ◀──────────── id_token ────────┤
│ │ 5. validate iss/aud/exp/nonce
│ │ 6. provision/find user, map
│ │ security groups → roles
│ ◀── logged in, role OK ──┤
Security design decisions
| Threat | Mitigation |
|---|---|
| CSRF on the callback | state parameter, single-use transient consumed on first read, 5-minute TTL |
| Token replay | Nonce claim validated with hash_equals against the state-bound nonce |
| Code interception | PKCE (S256) on the authorization request |
| Secret leakage from DB | Client secret encrypted AES-256-GCM; key injected from Azure Key Vault via env constant |
| Open redirect | wp_safe_redirect() only; no client-controlled redirect targets |
| Privilege creep | Roles re-mapped from security groups on every login; stale roles removed |
| Unauthorized accounts | Optional allowed_groups gate — login refused unless user is in a permitted group |
| Password attacks | Optional SSO-only mode disables password authentication entirely |
Group → role mapping
Configured in Settings → Entra SSO as Entra group Object IDs → WordPress roles:
| Entra security group | WP role |
|---|---|
HR-Portal-Admins object ID |
editor |
Intranet-Authors object ID |
author |
All-Staff object ID |
subscriber (default) |
On each login the plugin reads the groups claim, resolves every matching role, and replaces the user's roles — so directory changes take effect on next login with no cron job needed.
Entra ID app registration (setup guide)
- Entra admin center → App registrations → New registration
- Name:
Intranet WordPress SSO; supported accounts: Accounts in this organizational directory only - Redirect URI (Web):
https://intranet.example.com/wp-login.php?action=entra_sso_callback - Certificates & secrets → New client secret → copy the value
- API permissions:
openid,profile,email,User.Read(delegated) — grant admin consent - Token configuration → Add groups claim: Security groups → Group ID (puts group Object IDs into the
groupsclaim; for users in 200+ groups, switch to thehasgroups+ Graph fallback pattern) - Paste Tenant ID, Client ID and secret into the plugin settings page
Demo recording script (for the screening answer)
- Show the settings page briefly (tenant/app IDs, group mapping — no secret visible)
- Open a logged-out incognito window →
wp-login.php→ click Sign in with Microsoft - Show the redirect to
login.microsoftonline.com(address bar visible) → enter test account → MFA prompt - Land back in WordPress admin → open Users → highlight the provisioned user + assigned role
- In the Entra admin center, remove the user from the mapped group → log in again → role reverted (proves live group→role sync)
Files
wp-entra-sso.php Plugin bootstrap
includes/class-options.php Encrypted settings (AES-256-GCM, Key Vault-ready)
includes/class-oidc-client.php PKCE auth URL, token exchange, ID-token validation
includes/class-provisioning.php User provisioning + security-group→role mapping
includes/class-login-flow.php Login button, single-use state callback, redirects
includes/class-settings-page.php Admin UI