dportal / drupal_sso_login
Free, self-hosted OpenID Connect and Microsoft Entra ID SSO integration for Drupal.
Package info
github.com/gecille87/drupal-sso-login
Type:drupal-module
pkg:composer/dportal/drupal_sso_login
Requires
- firebase/php-jwt: ^6.10
Requires (Dev)
- drupal/core: ^9.5 || ^10 || ^11
- phpunit/phpunit: ^9.6 || ^10 || ^11
This package is auto-updated.
Last update: 2026-08-18 07:21:41 UTC
README
SSO Login is a free, self-hosted Drupal module for Microsoft Entra ID using OAuth 2.0 Authorization Code flow, OpenID Connect, PKCE, state, nonce, JWKS, and validated ID tokens. The provider boundary is intentionally small so another OIDC provider can be added without changing provisioning or the login UI.
Requirements
- Drupal 9.5+, 10, or 11
- PHP supported by the installed Drupal core version (PHP 8.0+ for this module)
- A Microsoft Entra app registration
- Composer
The module does not require a paid Drupal authentication service or proprietary SDK. firebase/php-jwt is used for maintained JWT/JWK validation.
Installation
This repository is published as the Composer package dportal/drupal_sso_login, but the Drupal module machine name is sso_login.
Step 1: Install via Composer
From a Drupal project root:
composer require dportal/drupal_sso_login
This installs the module code to web/modules/contrib/drupal_sso_login.
Step 2: Enable the Module
drush en sso_login -y drush cr
The module is now enabled. You can verify it at /admin/modules (search for "SSO Login").
Step 3: Access the Configuration
Navigate to Administration → People → SSO Login or visit /admin/config/people/sso-login directly. You should see the SSO Login configuration form.
Note: If the menu link doesn't appear, clear your browser cache and reload, or run
drush cragain to clear Drupal caches.
For Local Development
Place this directory at web/modules/custom/sso_login and enable it with Drush using the module name sso_login:
drush en sso_login -y drush cr
Microsoft Entra Setup
Prerequisites
- Access to Microsoft Entra admin center
- Global Administrator or Application Administrator role
- Client secret management (securely store the value immediately after creation)
Step-by-Step Instructions
1. Create an App Registration
- Go to Microsoft Entra admin center.
- Select Applications → App registrations → New registration.
- Enter a name (e.g.,
Drupal SSO). - Select Supported account types appropriate for your tenant:
- My organization only (Single tenant) – most common for internal SSO
- Any Azure AD directory + personal Microsoft accounts (Multitenant)
- Click Register.
2. Get Your Credentials
After app registration, note the following from the Overview page:
- Application (client) ID – copy this value
- Directory (tenant) ID – copy this value
3. Create a Client Secret
- In the app registration, go to Certificates & secrets → Client secrets → New client secret.
- Enter a description (e.g.,
Drupal Module). - Set expiration (e.g., 12 or 24 months; plan rotation before expiry).
- Click Add.
- Immediately copy the secret value (you cannot retrieve it again).
4. Configure the Redirect URI
- In the app registration, go to Authentication.
- Under Platform configurations, select Add a platform → Web.
- Enter the Redirect URI:
- Navigate to
/admin/config/people/sso-loginin your Drupal site. - Copy the exact Callback URL displayed (e.g.,
https://example.com/sso-login/callback/microsoft). - Paste this into the Entra redirect URI field.
- Navigate to
- Check ID tokens and Access tokens under Implicit grant and hybrid flows.
- Click Configure.
Important: The redirect URI must match exactly, including protocol (HTTPS), domain, and path. Mismatches cause AADSTS50011 errors.
5. Configure API Permissions (Optional but Recommended)
- In the app registration, go to API permissions.
- Ensure User.Read is granted (usually added by default).
- If you plan to use group mappings, add Directory.Read.All (requires admin consent).
Configuring Drupal
- Navigate to Administration → People → SSO Login (
/admin/config/people/sso-login). - Fill in the following fields:
| Field | Description |
|---|---|
| Enable SSO Login | Checkbox to enable/disable SSO. Disable to troubleshoot without affecting end users. |
| Client ID | The Application (client) ID from Entra. |
| Tenant ID | The Directory (tenant) ID from Entra. |
| Client Secret | The client secret value. Stored in Drupal State, not configuration. Never commit to Git. |
| Use OIDC Discovery | Default: ON. Automatically fetches endpoints from Entra. Disable only if you need explicit endpoint control. |
| Authorization Endpoint | (Only if Discovery is OFF) The OIDC authorization URL. |
| Token Endpoint | (Only if Discovery is OFF) The OIDC token endpoint URL. |
| JWKS URI | (Only if Discovery is OFF) URL to the public key set for token validation. |
| Scopes | Comma-separated OpenID Connect scopes (default: openid,profile,email). |
Provisioning & Account Linking
| Field | Description |
|---|---|
| Create user account if not found | Default: OFF. If enabled, creates new Drupal users automatically when they first log in with valid SSO credentials. If disabled, accounts must be created manually, and users can only link existing accounts. |
| Link by email if verified | Default: OFF. If enabled and the ID token includes email_verified: true, automatically links the SSO account to an existing Drupal user with the same email (only if no link exists). Risky if email verification is not trusted. |
| Username pattern | Token-based pattern for generated usernames. Supports: {{ email }}, {{ preferred_username }}, {{ given_name }}, {{ family_name }}, {{ email_prefix }}. Example: {{ email_prefix }}-entra. Collisions are resolved with numeric suffixes. |
Role & Group Mapping
| Field | Description |
|---|---|
| Use group-based roles | Default: OFF. If enabled, users are assigned Drupal roles based on their Entra group membership. Requires group claims in the ID token. |
| Groups claim name | The claim in the ID token containing groups (usually groups). |
| Group-to-role mapping | JSON mapping of Entra group IDs to Drupal role machine names. Example: {"00000000-0000-0000-0000-000000000000": "editor", "11111111-1111-1111-1111-111111111111": "administrator"}. Only add roles you explicitly trust. |
Profile Mapping
| Field | Description |
|---|---|
| Map additional profile fields | JSON mapping of ID token claims to Drupal user profile fields. Example: {"mobile_phone": "field_phone", "office_location": "field_office"}. Requires custom profile fields to exist. |
Login Button & Block
| Field | Description |
|---|---|
| Button text | Text displayed on the login button (default: Sign in with Microsoft). |
| Button CSS class | Additional CSS classes for styling. Example: btn btn-primary. |
- Click Save after configuring.
Using SSO Login
For End Users
- Navigate to
/user/login(or click Sign in link). - A button labeled "Sign in with Microsoft" (or custom text) appears.
- Click the button.
- You are redirected to Microsoft Entra to authenticate.
- After successful authentication, you are redirected back to Drupal and logged in.
- On first login (if provisioning is enabled), a new account is created with the configured username pattern.
For Site Administrators
- Disable SSO temporarily: Turn off "Enable SSO Login" to troubleshoot without affecting users.
- View linked accounts: Navigate to Administration → People and edit a user. Linked SSO identities are displayed.
- Unlink accounts: Site administrators cannot yet unlink via UI (planned feature); use database access if needed.
- View diagnostics: Navigate to Administration → People → SSO Login → Diagnostics to verify OIDC discovery, endpoints, and JWKS validation.
Configuration Examples
Example 1: Automatic User Provisioning with Email
Enable SSO Login: ☑
Client ID: abc123...
Tenant ID: xyz789...
Create user account if not found: ☑
Link by email if verified: ☑
Username pattern: {{ email_prefix }}-entra
Use group-based roles: ☐
Result: On first login, users are provisioned automatically with a username like john.doe-entra from their email.
Example 2: Manual Account Management with Role Mapping
Enable SSO Login: ☑
Client ID: abc123...
Tenant ID: xyz789...
Create user account if not found: ☐
Link by email if verified: ☐
Use group-based roles: ☑
Groups claim name: groups
Group-to-role mapping: {
"00000000-0000-0000-0000-000000000001": "editor",
"00000000-0000-0000-0000-000000000002": "administrator"
}
Result: Administrators must manually create Drupal accounts and link them to SSO. Roles are assigned based on Entra group membership.
Example 3: Profile Field Synchronization
Map additional profile fields: {
"department": "field_department",
"office_location": "field_office",
"mobile_phone": "field_phone"
}
Result: User profile fields are populated from Entra claims on each login.
Configuration Details
The main form is /admin/config/people/sso-login. It supports discovery, scopes, user provisioning, account linking, username patterns, profile claim mappings, group mappings, and button text/class. The default configuration is deliberately conservative: SSO is disabled, local login remains available, and email linking is disabled.
Username patterns support tokens such as {{ preferred_username }}, {{ email }}, {{ given_name }}, and {{ family_name }}. Generated names are normalized and collisions receive deterministic numeric suffixes.
Only explicitly configured mappings can assign roles. Validate group claims and avoid mapping external values to administrator unless that is an intentional, reviewed policy.
Secrets and Configuration Management
Client Secret Storage
Important: Do not commit client secrets to Git or version control.
- The module stores the client secret in Drupal State (database), not in exportable configuration.
- If you leave the Client Secret field blank on a save, the previous value is preserved.
- For production deployments:
- Protect database access with strong credentials.
- Consider using Drupal Key module or environment variable integration for secret management.
- Rotate secrets periodically before expiration.
- Use separate Entra app registrations for development, staging, and production.
Automatic Email Linking Security
- Email-based account linking requires the
email_verifiedclaim in the ID token. - This claim must come from the identity provider (Entra) and is validated on each login.
- Enable email linking only if you trust the provider's email verification.
- Verify in the Diagnostics page that
email_verifiedis present and true in your token.
Security Features
The module implements defense-in-depth security:
| Feature | Purpose |
|---|---|
| Server-side State | Single-use state stored in Drupal PrivateTempStore; prevents replay attacks. |
| Nonce Validation | Issued before redirect and validated in the returned token. |
| PKCE S256 | Authorization code exchange with code challenge; protects against authorization code interception. |
| HTTPS Verification | HTTP client verifies SSL certificates; prevents MITM attacks. |
| OIDC Discovery | Automatically fetches and validates endpoints from the identity provider. |
| JWT Signature Validation | All tokens verified against provider's public key set (JWKS). |
| Issuer/Audience/Tenant Validation | Token claims verified to match configuration; prevents token misuse. |
| Time Validation | Checks token expiration and clock skew tolerance. |
| Blocked Account Checks | Denies login for disabled Drupal user accounts. |
| Secure Destinations | Post-login redirects remain internal to prevent open redirects. |
| No Token Persistence | Access and refresh tokens are not stored; session-only. |
Testing and Development
Local Development
Place this directory at web/modules/custom/sso_login:
cd web/modules/custom git clone <repo-url> sso_login cd sso_login
Running Tests
# Validate Composer metadata composer validate # Check PHP syntax composer run lint # Run unit tests (no live account required) composer run test # Run with Drupal functional tests (requires local Drupal test site) cd /path/to/drupal php vendor/bin/phpunit -c core/phpunit.xml.dist modules/contrib/drupal_sso_login/tests
Mocking Provider Responses
For functional testing:
- Mock the HTTP client to return test tokens instead of calling live Entra.
- Create valid test JWT tokens using
firebase/php-jwtwith test keys. - Test user provisioning, role mapping, and profile field updates.
Troubleshooting Guide
Error: "AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application."
Cause: The redirect URI in Entra does not exactly match Drupal's callback URL.
Solution:
- Navigate to
/admin/config/people/sso-loginand copy the exact Callback URL shown. - In Entra, go to Authentication and verify the registered Web redirect URI matches exactly (including protocol, domain, and path).
- Clear Drupal caches:
drush cr. - Try logging in again.
Error: "Invalid client" or "Client secret is invalid"
Cause: Client ID or secret is incorrect or expired.
Solution:
- In Entra, verify the Application (client) ID is correct.
- If the secret is expired, create a new one in Certificates & secrets.
- Update Drupal configuration with the new secret.
- Run
drush crto clear caches.
Error: "Invalid issuer" or "Invalid audience"
Cause: Token issuer, audience, or tenant ID does not match configuration.
Solution:
- Visit
/admin/config/people/sso-login/diagnosticsto see the discovered OIDC endpoints and issuer URL. - Verify the Tenant ID matches the one in your token.
- Verify the Client ID in Drupal matches the Entra app registration.
- If discovery is disabled, ensure the token endpoint and JWKS URI are correct.
Error: "No user account found" or "User provisioning disabled"
Cause: A new user tried to log in, but provisioning is disabled or account linking is not configured.
Solution:
- Either enable Create user account if not found to auto-provision, or
- Manually create a Drupal account first and configure account linking (Link by email if verified), or
- Run the Drush command to link the account manually (if implemented).
Error: "User account is blocked"
Cause: The Drupal user exists but is blocked.
Solution:
- Unblock the user at Administration → People or via Drush:
drush user:unblock <username>
- The user can now log in with SSO.
Error: "Missing roles" after login
Cause: Group-based role mapping is enabled, but user groups don't match any configured mapping.
Solution:
- Visit
/admin/config/people/sso-login/diagnostics. - Check the validated ID token's
groupsclaim. - Add the group IDs to the Group-to-role mapping in the configuration form.
- Verify the Drupal role machine name exists.
- Run
drush crand try logging in again.
Debugging with Diagnostics Page
Navigate to Administration → People → SSO Login → Diagnostics (/admin/config/people/sso-login/diagnostics) to:
- View the OIDC discovery endpoint results.
- Inspect decoded (unsecured) ID tokens (for testing only).
- Verify JWKS endpoint accessibility.
- Confirm OIDC discovery is working correctly.
Uninstallation and Privacy
Uninstallation
Run:
drush pm:uninstall sso_login -y
What is removed:
- Module configuration
sso_login_identitydatabase table
What is NOT removed:
- Drupal user accounts
- Drupal roles
- Drupal content
- Custom user profile fields
Data Privacy
The sso_login_identity table stores:
- Provider type (e.g., "microsoft")
- Subject (unique user ID from provider)
- Tenant ID
- Timestamps (created, updated)
- Claim metadata (for debugging/audit)
Recommendations:
- Include this data in your site's privacy policy.
- Implement a user deletion workflow that removes
sso_login_identityrecords when users request account deletion. - Audit the data regularly.
- Consider implementing GDPR compliance (right to be forgotten).
GDPR Compliance
To support GDPR data deletion:
- Create a Drush command or admin UI to remove a user's identity association.
- Delete the corresponding row from
sso_login_identitytable. - Optionally anonymize user profile fields that were synced from the provider.
Example query (for privileged use only):
DELETE FROM sso_login_identity WHERE mail = 'user@example.com';
License
GPL-2.0-or-later.