Supporting Multiple Identity Providers Per Tenant
The moment a business customer asks to sign in with their own identity provider, a single-provider integration becomes a multi-tenant one, and several assumptions that were safe stop being safe. This page is part of the configuring identity providers for OIDC guide.
Home-Realm Discovery, Done Safely
The user types an email address, you map its domain to a tenant, and you send them to that tenant’s provider. The mapping is the security boundary, and it has to be earned rather than claimed.
Require proof before a domain routes anywhere: a DNS TXT record, or a challenge sent to an address at that domain and confirmed. Re-verify periodically, because domains change hands, and remove the routing immediately when verification lapses.
Never route on a domain the user can influence beyond typing it. The email address is a hint that selects a configured mapping; it is not itself the mapping.
The Issuer Check Becomes Per-Tenant
In a single-provider integration the expected issuer is a constant. With several providers it must be resolved from the tenant the login belongs to, and getting this wrong is the flaw that lets one customer mint tokens your application accepts for another.
// The expected issuer is a property of the tenant, never a global constant.
export async function verifyForTenant(token: string, tenantId: string) {
const cfg = await providerConfig.forTenant(tenantId); // registered, not discovered from the token
if (!cfg) throw new Error("tenant has no provider configured");
const { payload } = await jwtVerify(token, cfg.jwks, {
issuer: cfg.issuer, // this tenant's issuer, exactly
audience: cfg.clientId, // this tenant's client
algorithms: cfg.algorithms, // per-tenant allowlist
});
return payload;
}
Resolve the tenant from your own flow state — the value you stored when the login started — rather than from a claim inside the token. A token that tells you which tenant to validate it against is validating itself, which is not validation.
Keep the per-tenant key sets separate, as described in caching JWKS without breaking key rotation. One merged key set across all tenants would let a token claiming one issuer be verified with another’s key, which collapses the boundary entirely.
Claims Arrive From Systems You Do Not Control
Group and role claims come from a directory the customer administers, so they are untrusted input in the strict sense: the customer decides their contents, and a customer administrator should not be able to grant themselves capabilities in your product by renaming a group.
Decide explicitly whether the directory is authoritative. If it is, roles are recomputed from claims on every login and local grants are overwritten; if it is not, claims seed the initial roles and local grants persist. Both are defensible and the mixture is not, because nobody can then predict what a login will do to someone’s access.
Failures Become Per-Tenant Too
With one provider, an outage is an outage. With fifty, one customer’s provider misbehaving must not look like a general failure — and must not be diagnosed by reading undifferentiated logs.
Tag every authentication event with the tenant and the provider, and track error rates per tenant. A spike confined to one tenant is their configuration or their provider; a spike across all of them is yours. That single dimension turns “logins are failing” into an answerable question.
Give support a per-tenant diagnostic view showing the configured issuer, the last successful login, the last error and its reason. Most tickets about single sign-on are resolved by looking at one of those four values, and exposing them removes an engineering round trip.
Set per-tenant rate limits on the token endpoint too, so one customer’s misbehaving client — a retry loop against their own provider — cannot consume the capacity everyone else depends on.
Provider Configuration as Data
Store client secrets by reference to a secrets manager rather than in the record itself, so the configuration can be read, exported and reviewed without exposing credentials. Keep the verification proof and its date alongside the domain list, because “when was this domain last confirmed?” is a question that arises during exactly the incident you would rather not be investigating.
Onboarding Without Engineering Involvement
Every new customer provider should be self-service, or the feature becomes a queue. The pieces are a registration form capturing the issuer, client identifier and secret; a domain verification step; a claim-mapping editor; and a test-login button that runs the full flow and reports exactly where it failed.
That test button is the highest-value part. Most misconfigurations — a redirect URI not registered on their side, a missing claim, an unexpected signing algorithm — are found in seconds by attempting a login and surfacing the real error, and never found at all by reading a form.
Store the configuration like any other infrastructure: versioned, auditable, with a record of who changed what. A customer’s identity configuration is a security boundary, and a change to it deserves the same trail as a permission change.
Session Policy Also Becomes Per-Tenant
Customers who bring their own identity provider usually bring their own session expectations with it, and those requirements arrive during procurement rather than during implementation.
Make idle and absolute session lifetimes configurable per tenant, with a default that suits your product and a range you are willing to support. An enterprise customer requiring a fifteen-minute idle timeout should be a configuration change rather than a conversation, and a customer wanting a thirty-day remember-me should be refused politely rather than accommodated silently.
The same applies to step-up rules and to whether a provider’s own multi-factor result is trusted. If the customer’s provider asserts that a second factor was used, decide explicitly whether that satisfies your step-up requirement or whether you demand your own — and record the decision per tenant, because it differs and because an auditor will ask.
Finally, make “sign out everywhere” work across the boundary. A customer disabling a user in their directory expects that user’s sessions in your product to end, which requires either back-channel logout or a periodic check against the provider. Without one of those, deprovisioning at their end leaves live sessions at yours — which is a gap customers discover during their own offboarding tests and reasonably treat as a defect rather than a limitation.
Frequently Asked Questions
Can I identify the tenant from the token instead of the flow state?
No. Deciding which configuration to validate a token against based on a value inside that token means the token selects its own verification rules, which is not verification. Carry the tenant in the server-side flow state you created when the login started, and validate strictly against that tenant’s registered issuer, audience and algorithms.
What if two customers claim the same email domain?
Only one can hold a verified claim, and verification is what settles it. Consumer domains such as common webmail providers should never be claimable at all — maintain a denylist, or a single customer verifying a shared domain would capture every user with that suffix. Where a genuine conflict arises, resolve it manually rather than automatically.
Should users be able to choose their provider?
Prefer routing by verified domain, with an explicit chooser only where a user legitimately belongs to several tenants. A free choice of provider lets a user pick the one with the weakest controls, which defeats the point of a customer configuring theirs. Where a chooser is necessary, list only providers the user is actually associated with.
How do I handle a customer whose provider only supports HS256?
Keep verification in one trusted component, never distribute the shared secret to resource servers, and store it as a secret rather than as configuration. Record it as a per-tenant exception with a review date, and press for asymmetric signing — a symmetric key means every holder can mint tokens as well as verify them, which is a poor fit for a boundary between organisations.
What happens when a customer rotates their signing key?
Nothing, if their key set is cached per issuer with a bounded lifetime and a refetch on an unknown key identifier. That is exactly why per-tenant caching matters: a rotation by one customer should be invisible to you and to everyone else, and a shared or unbounded cache turns it into a support ticket from one tenant and confusion for the rest.
Document which of these behaviours you support per tenant, and keep that document next to the provider configuration. Enterprise procurement asks the same handful of questions about session lifetime, factor trust and deprovisioning every time, and a written answer that matches what the code actually does shortens the conversation considerably.
Related
- Configuring identity providers for OIDC — discovery, registration and validation for a single provider.
- Mapping OIDC claims to application roles — the allowlist that turns directory groups into permissions.
- Multi-tenant authorization and data isolation — the boundary a per-tenant issuer check protects.