Migration — 0.4.0 EntraExternalId companion deprecation
Migration — 0.4.0 EntraExternalId companion deprecation
Status. Soft deprecation in 0.4.0. The EntraExternalId (server) + EntraExternalIdClient (client) companions continue to compile and ship; the README + module-header doc comments carry a deprecation banner pointing consumers at the new path. Removal is scheduled for 0.Y.0.
What changed
The 0.4.0 OidcAppConfig unification (see 0.4.0-oidc-app-config.md) ships OidcPresets.entraExternalId + OidcPresets.entraExternalIdWithDomain as the recommended path for Entra External ID (CIAM) deployments. The preset returns an OidcAppConfig consumed by the standard OidcAuthUI shell — no CustomAuthUI wrapping, no companion-specific config record.
The dedicated companion remains the right choice for two specific use cases the single-call preset doesn't cover:
| Use case | Why companion still wins | Preset adequate? |
|---|---|---|
Sign-up / sign-in user-flow policy routing (SignUpPolicyId / SignInPolicyId) — surfaces a "Sign up" button alongside "Sign in" that routes through a separate Entra User Flow. |
Single-call preset has no policy-routing surface; sign-up requires the explicit shell-level "Sign up" affordance the companion provides. | No. |
Server-side oid → UserId / tid → TenantId claim remapping — External ID's oid is more stable than sub (which varies per app registration). |
Generic OidcAuthProvider uses sub → UserId; remapping to oid is not yet exposed as a first-class option. Tracked separately as a substrate seam. |
No. |
For everything else — basic sign-in against an External ID tenant, with or without a custom CIAM domain — the preset is now the recommended path.
Worked migration — sign-in-only consumer
Pre-0.4.0:
// Client side
let externalIdCfg =
EntraExternalIdClientConfig.create
"<tenant-subdomain>"
"<client-id>"
"https://app.example.com/auth/callback"
ClientConfig.compose
{| ...
AuthUI = CustomAuthUI { Wrap = EntraExternalIdAuthUI.wrap externalIdCfg } |}
// Server side
let externalIdServerCfg =
EntraExternalIdConfig.create "<tenant-subdomain>" "<audience>"
ServerApp.empty
|> ServerApp.withAuth (EntraExternalIdAuthProvider.create externalIdServerCfg ...)
0.4.0 equivalent:
// Client side
let oidcCfg =
OidcPresets.entraExternalId
"<tenant-subdomain>"
"<client-id>"
"https://app.example.com/auth/callback"
ClientConfig.compose
{| ...
AuthUI = OidcAuthUI (OidcAppConfig.toClientConfig oidcCfg) |}
// Server side
let oidcServerCfg : OidcAuthProviderConfig =
{ Issuer = oidcCfg.Issuer // SAME issuer the client side built — single source of truth
Audience = oidcCfg.Audience
ClientId = oidcCfg.ClientId
... }
ServerApp.empty
|> ServerApp.withAuth (OidcAuthProvider.create oidcServerCfg ...)
The boilerplate around projecting OidcAppConfig to the server-side OidcAuthProviderConfig is a one-cycle bridge — a follow-up commit lands OidcAppConfig.toServerConfig so consumers write OidcAuthProvider.create (OidcAppConfig.toServerConfig oidcCfg) ... directly.
Worked migration — custom CIAM domain consumer
Pre-0.4.0:
let externalIdCfg =
{ EntraExternalIdClientConfig.create "<tenant>" "<client-id>" "<redirect>"
with CustomDomain = Some "login.mybrand.com" }
0.4.0:
let oidcCfg =
OidcPresets.entraExternalIdWithDomain
"<tenant>"
"login.mybrand.com"
"<client-id>"
"<redirect>"
When to stay on the companion
Keep the existing wiring untouched when either of the following applies:
- Your sign-in shell surfaces a "Sign up" button that routes through a distinct Entra User Flow (
SignUpPolicyId). The companion'sEntraExternalIdAuthUI.wrapis the only path that exposes the dual-button affordance today. - Your server-side handlers depend on the
oidclaim being projected ontoAuthenticatedUser.UserId(rather thansub). The companion'sEntraExternalIdAuthProvideris the only path that performs this remapping today.
A follow-up SDK change adds either capability to the substrate (claim-remap seam on OidcAuthProvider; policy-routing variant of OidcPresets.entraExternalId); when that lands, the companion's last remaining use cases evaporate and the 0.Y.0 removal becomes safe.
Verification
dotnet build— companion code stays compiling under 0.4.0. No[<Obsolete>]attributes are applied (avoids warning noise for consumers who keep the companion intentionally); the deprecation surface is README + module-doc only.- Smoke-test sign-in against your External ID tenant. Behaviour unchanged.
- Plan for 0.Y.0 — when you do migrate, the diff is mechanical per the worked examples above. The new path runs against the same External ID tenant + app registration; no Entra-side configuration changes required.
Rollback
The deprecation is documentation-only at 0.4.0. There is nothing to roll back at the consumer end. Future-you wanting to migrate can follow this doc; future-you wanting to stay can ignore the README banners until the companion's 0.Y.0 removal lands.
Related
0.4.0-oidc-app-config.md— the unified config + preset migration this deprecation aligns with.0.3.x-oidc-presets.md— the 0.3.x advisory introduction of presets, including the External ID preset that pre-dated the unified config.