Headless IdP authenticates a decoupled Drupal site against an external identity provider, so you don't have to write the JWT-validation, MFA and account-linking glue yourself. The provider handles passwords, MFA and the user lifecycle; Drupal validates the token and runs as your API backend.
Features
- Four providers ready to use. AWS Cognito, Okta and two Microsoft Entra providers - workforce Entra ID and Entra External ID (customer identity / CIAM) - ship with the module, and a plugin API lets you add more. Keycloak and Supabase are on the 1.x roadmap, so you're not tied to one vendor.
- The whole auth lifecycle, not just login. Password sign-in, MFA challenge and response, user enrolment, session revocation, email and password sync, and refresh-token rotation.
- Headless-first. REST endpoints, JSON in and out, Bearer JWTs (an id_token or access token, depending on the provider). No redirect flows, no session cookies and no OAuth login screens to theme. Your frontend (Nuxt, Next, or anything that speaks HTTP) handles all of that.
- Security you don't have to build. JWKS signature verification, algorithm-confusion rejection, strict issuer and audience binding, per-user, per-IP and per-session brute-force rate limiting, a configurable password-complexity policy, and length caps. Defence-in-depth from the start, with a dedicated security-kernel test suite.
- Extensible by interface. Each provider declares which capabilities it supports and the module adapts. Adding a provider or a capability never breaks the ones already shipped.
- Operator tooling. Drush commands to inspect providers, manage the Drupal-to-IdP account links, migrate from openid_connect, and manage MFA preferences. Backed by over 1,000 unit and kernel tests.
Why use it
It's built for teams running a Nuxt, Next or other SPA frontend on a Drupal backend who want their external IdP to handle credentials, MFA and the lifecycle, with Drupal as the policy and API layer. The provider does the security-sensitive work; you get a clean, headless, provider-agnostic integration instead of bespoke glue code.
Why I built it
Over five years of decoupled Drupal builds, I kept solving the same authentication problem. On all of them Drupal itself was the authenticator - the frontend signed users in against a Drupal endpoint through simple_oauth, and every site had its own local user pool. When the company moved towards ISO 27001 compliance, those separate pools had to merge into one shared AWS Cognito pool, with attributes controlling which platforms each user could reach. The identity provider now owned credentials, MFA and the audit trail, not Drupal.
You can't bulk-migrate hashed passwords - they're one-way, so that move had to run lazily, at each user's next login. That, and everything around it - JWT validation, issuer and audience pinning, algorithm-confusion rejection, the MFA round-trip, linking each external identity to a Drupal user and keeping them in sync - is fiddly, security-sensitive work where subtly wrong means a hole.
This module is that work done once, properly - the distillation of what those years taught me. The provider quirks and the production edge cases are baked into the capability interfaces and the security-kernel test suite, so the hardening comes as standard rather than something you retrofit under audit pressure. Making that kind of migration turnkey - moving an existing Drupal user pool onto the IdP lazily at login - is on the roadmap.
How it works
Your frontend signs the user in against the IdP and gets a JWT back, then sends that token to Drupal as a Bearer credential. Drupal verifies the signature against the provider's published keys (JWKS), links the token to a Drupal user through the externalauth module, and treats the request as authenticated.
Requirements
- Drupal 10.2 or 11, PHP 8.1 or later
- simple_oauth, externalauth, and firebase/php-jwt
- The AWS SDK (aws/aws-sdk-php) when you enable the Cognito sub-module. The Okta and Entra sub-modules use Guzzle, which already ships with Drupal.
Getting started
Enable the base module and at least one provider sub-module (headless_idp_cognito, headless_idp_okta, headless_idp_entra or headless_idp_entra_external), then run drush cache:rebuild. Configure it at Configuration > People > Headless IdP. Each provider's own setup is documented in its sub-module README, and a reference Nuxt 3 frontend composable ships in the module's docs.
Documentation
- Module README - installation, configuration, endpoint reference and the capability table.
- AWS Cognito provider README - user-pool setup, IAM policy and operator recipes.
- Okta provider README - org setup, API token scopes and the setup FAQ.
- Microsoft Entra ID (workforce) provider README - app registration, token validation and Graph session revocation.
- Microsoft Entra External ID (CIAM) provider README - Native Authentication sign-in, MFA, Graph admin setup and the setup FAQ.
How it compares
- OpenID Connect is redirect-flow OIDC for server-rendered Drupal. Headless IdP is Bearer-token and headless-first instead, and provider-agnostic through capability interfaces.
- JWT mints and validates Drupal-issued JWTs. Headless IdP validates JWTs minted by an external IdP.
Project information
- Project categories: Access control, Decoupled, Integrations
- Ecosystem: Externalauth Gitlab OAuth2 connector, simple_oauth_authentication_extender
- Created by matt-whelan on , updated
Stable releases for this project are covered by the security advisory policy.
There are currently no supported stable releases.
Releases
Third beta. It adds two Microsoft Entra providers - workforce Entra ID and Entra External ID (customer identity / CIAM) - alongside the existing AWS Cognito and Okta support, plus an optional admin-set password on enrolment and a login length cap.
