toolup-forgetoolup-forge

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 oidUserId / tidTenantId claim remapping — External ID's oid is more stable than sub (which varies per app registration). Generic OidcAuthProvider uses subUserId; 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:

  1. Your sign-in shell surfaces a "Sign up" button that routes through a distinct Entra User Flow (SignUpPolicyId). The companion's EntraExternalIdAuthUI.wrap is the only path that exposes the dual-button affordance today.
  2. Your server-side handlers depend on the oid claim being projected onto AuthenticatedUser.UserId (rather than sub). The companion's EntraExternalIdAuthProvider is 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

  1. 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.
  2. Smoke-test sign-in against your External ID tenant. Behaviour unchanged.
  3. 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.

  • 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.