ADR-004 — Alternative auth providers
Shipped 2026-05-25. All five phases landed; the legacy Entra-only columns were dropped after a clean deploy cycle.
Context
ADR-003 made the Entra tenant per-Brand (BYO), but the provider was still hard-coded across the stack: the User schema knew entraOid, the validator imported createEntraTokenValidator, and the console used MSAL exclusively. Future resellers don't all live on Microsoft. The shortest path to “a Brand signs in with whatever IdP they already have” was to add three sibling providers as first-class options.
Decision
One AuthProvider interface, four implementations: Entra, Google Workspace, Okta, Auth0. Each Brand picks exactly one. The existing JwtValidator cache becomes an AuthProvider cache keyed by (brandId, providerId).
interface AuthProvider {
readonly id: 'entra' | 'google' | 'okta' | 'auth0';
validateToken(rawToken: string, cfg: ProviderConfig):
Promise<ProviderClaims | null>;
}
interface ProviderClaims {
externalId: string; // Entra oid, Google sub, Okta sub, Auth0 sub
email: string | null;
name: string | null;
raw: Record<string, unknown>;
}Out of scope
- Facebook. Unusual for B2B SaaS; revisit if a Brand explicitly asks.
- GitHub. OAuth-not-OIDC + email-visibility constraints don't justify the special case without a Brand asking.
- Multi-provider per Brand. A Brand using Google for some users and Okta for others — defer to a separate ADR if it ever comes up.
- SAML natively. Auth0 and Okta both federate SAML for the Brands that need it; we don't speak it directly.
Phase plan
- Phase 1 — Schema + AuthProvider interface.
authProvider+authProviderConfigJsonon Brand;authOidon User. Entra implementation only. - Phase 2 — Google + Okta + Auth0 validators. OIDC discovery, JWKS cache, post-validate checks.
- Phase 3 — Console sign-in dispatch.
oidc-client-tsfor non-Entra Brands; OIDC callback at/b/<slug>/auth/callbackseeds the access token. - Phase 4 — Admin auth-provider config UI. Brand-Owner picks a provider + pastes config; the UI validates before commit.
- Phase 5 — Backfill + read-switch. Existing Entra Brands carry on unchanged; non-Entra Brands route through the new columns. Legacy Entra-only columns dropped in #175.
Provider-specific post-validate checks
- Entra.
issmatches the Brand's tenant id;audmatches the Brand's client id. - Google.
hdclaim equals the Brand'shostedDomain. - Okta.
issmatches the Brand's issuer URL; JWKS via.well-known/openid-configuration. - Auth0.
audclaim equals the Brand's configured audience.
References
Full markdown: adr-004-alt-auth-providers.md.
Related: Concepts — BYO identity, How-to — Set up BYO Entra.