Prospective case · Identity as code
Moving a regulated insurer from Okta to Microsoft Entra ID, as code
A large regulated insurer licensed Microsoft 365 and Entra ID, and paid separately for Okta to sign its people in. The migration is designed so that every application, group and access policy moves through a reviewed pipeline, and the Entra ID tenant that remains is managed as code rather than by console.
Situation
Two identity providers, one of them already paid for
Identity was mastered in on-premises Active Directory and synchronized to two cloud directories at once: Okta, which signed users in to several hundred SAML and OIDC applications and provisioned dozens more, and an Entra ID tenant that Microsoft 365 required in any case. The Microsoft 365 domain was federated to Okta, so every Microsoft sign-in made a round trip through the second provider.
The Entra ID tenant was not empty. Several years of Conditional Access policy had been built in it by hand, and nobody could say with confidence which policies were still doing work. Any migration plan that treated the target as a blank tenant would have overwritten controls the business depended on.
The insurer was regulated, so the migration had to produce evidence as it went: who changed each control, when, and under which change record. And it had to be reversible at every step, because the characteristic identity outages — administrators locked out, a policy enforced before it was observed, a change larger than intended — are the ones a migration is most likely to cause.
Cost and benefit
What moving off Okta is worth to a Microsoft estate
Two separate economic questions sit inside this decision, and they are worth keeping apart. The first is the license: what the organization stops paying when Okta is retired, net of anything Entra ID needs that is not already owned. The second is the operating model: what it costs to run the identity platform by console against running it as code. Either can justify the program on its own; the strongest cases have both.
The license
For an organization that already runs Microsoft 365, the identity license for Entra ID is usually paid for before the migration begins. Microsoft 365 E3 and Business Premium include Entra ID P1, which carries Conditional Access, group-based application assignment and hybrid identity. Microsoft 365 E5 includes Entra ID P2, which adds risk-based Conditional Access, Privileged Identity Management and access reviews. In either case, Okta is a second identity provider licensed on top of an entitlement the organization already holds.
The chart compares the annual identity license per user before and after the move, at an illustrative Okta rate of $14 per user per month. The one case in which Entra ID adds cost — an E3 estate that needs P2 features — still leaves a margin, and it is the case in which the cheaper answer is often an E5 security add-on that brings the Defender and Purview capabilities described below with it.
What the license line leaves out
- The saving begins at Okta contract exit, not at the first wave. Okta stays live as the fallback for the length of the program, so the two providers run side by side for most of a year.
- Okta features with no license-free equivalent must be costed: workflow automation, device assurance and any desktop MFA agent each need a named replacement (Intune compliance, Logic Apps or the existing governance platform) before the contract can end.
- Use the contracted Okta rate and the real Microsoft 365 mix. The model is only as good as those two inputs.
Identity license beyond Microsoft 365, per user per year
- Today: Okta alongside Microsoft 365$168
Illustrative Okta rate; substitute the contracted one.
- After: Microsoft 365 E5$0
Entra ID P2 is already included.
- After: Microsoft 365 E3, P1 sufficient$0
Entra ID P1 is included: Conditional Access, group-based assignment, hybrid identity.
- After: Microsoft 365 E3, P2 required$120
Standalone Entra ID P2 at its $10 list price, for risk-based Conditional Access and Privileged Identity Management. Conservative: it pays again for the P1 already held.
The operating model
The second question is how the platform is run after the migration. The executive brief prices it with a total-cost-of-ownership model that names every variable: change volume, cost per change under each practice, environments that must hold the same configuration, audit preparation, incident cost, key-person risk, the up-front investment and pipeline maintenance.
Under the brief's base inputs, managing the platform as code costs more in the first year and roughly half as much over five, breaking even at about thirteen months. Under inputs built deliberately to disfavor it, it still breaks even inside five years. The inputs are illustrative and are replaced with the organization's own before any decision is made.
The full model, with every variable, the conservative and aggressive cases and a sensitivity table, is in Configuration as an Asset.
Cumulative cost of running the identity platform
- By console
- As code
Show the figures
| Year | By console | As code |
|---|---|---|
| Start | $0 | $350,000 |
| 1 | $490,000 | $512,500 |
| 2 | $980,000 | $675,000 |
| 3 | $1,470,000 | $837,500 |
| 4 | $1,960,000 | $1,000,000 |
| 5 | $2,450,000 | $1,162,500 |
Approach
Four steps, one write path
The program runs in four steps. The first two write nothing through the pipeline: they establish what exists on both sides before anything moves. The third builds the only path by which Entra ID changes. The fourth moves applications in waves, with Okta kept as the fallback until each wave has proved itself.
Step 1
Discovery: read Okta into code
A read-only Okta service application, authenticating with a private key rather than an API token, exports the entire org nightly: applications, groups and group rules, sign-on and password policies, network zones, authorization servers and profile schemas. Terraform import generates the configuration, which is normalized into one file per object type. Per-user records go to restricted storage; only counts reach the repository. Each night's export is compared with the last, so any change made in Okta during the migration appears the next morning as a reviewable difference.
What it leaves behind
- A diffable specification of what must move
- A change-freeze monitor for the whole program
- Assignment counts that size each wave's blast radius
Step 2
The mapping layer: brownfield first, then translate
The existing Entra ID configuration is imported into code as it stands, every resource protected from deletion, until a plan shows no changes — proof the import is faithful. A decision workbook then rationalizes the hand-built Conditional Access policies: keep, rebuild into a persona framework, or both. Only then is each Okta object mapped, in a versioned catalog, to an Entra ID definition, a disposition, a wave and an owner, and a translator emits draft definitions for engineers to review. The authentication cutover — password hash sync, seamless sign-on, Staged Rollout groups and the MFA registration campaign — is designed here and gates every wave.
What it leaves behind
- The existing tenant under code, with nothing changed
- A mapping catalog with fidelity recorded per row
- Unresolved gaps that block a wave rather than surprise it
Step 3
The controlled write path
Three pipeline identities — read, plan and apply — authenticate with short-lived federated tokens and hold no stored secret. Every change is a pull request. Policy-as-code rules reject a plan that removes the break-glass exclusion, enforces a new Conditional Access policy without a report-only period, or destroys an imported object. A change gate verifies an approved change record whose requester is not the author. Changes apply to a rehearsal tenant first and to production only after approval. A nightly drift check opens an issue for any console change and never reverts one automatically.
What it leaves behind
- The answer to who changed a control, when, and why
- The classic identity outages prevented by rule
- Audit evidence generated as a by-product of the work
Step 4
Cutover waves and Okta decommission
Applications are risk-scored from inventory data and grouped by business unit under sponsor sign-off, starting with a pilot of IT staff. Each wave is validated with synthetic sign-in tests, error rates and owner sign-off over a window sized to criticality — 30, 14 or 7 days by tier. The Okta assignment is removed in a separate, later change, and Okta configuration is kept so any application can be pointed back. The domain conversion from federated to managed runs last, by runbook, with re-federation as its rollback.
What it leaves behind
- A rollback for every step that is a configuration change
- Okta retained until each validation window closes
- A hard decommission milestone with an executive owner
The mapping layer
Every Okta object, its Entra ID target, and what changes it
The mapping catalog is the center of the program. Every Okta object gets an Entra ID target and a record of who changes it afterwards. Fidelity is recorded per row — exact, approximate or manual — so nothing is lost in translation without being written down.
SAML and OIDC applications
Enterprise applications and app registrations
Terraform (azuread)SAML attribute statements
Custom claims policy per application
Beta Graph API; the portal edit is otherwise invisible to code.
Graph (msgraph)Groups and group rules
Existing AD-synced groups; dynamic groups
Matched by lookup, not recreated.
Terraform (azuread)Sign-on and authentication policies
Conditional Access policies, report-only first
Terraform (azuread)Network zones
Named locations
Terraform (azuread)Authenticators and enrollment
Authentication methods policy and strengths
Strengths in azuread; the methods policy needs Graph.
Graph (msgraph)Admin roles
Directory roles under Privileged Identity Management
Eligible, never standing.
Terraform (azuread)Authorization server scopes
Exposed API scopes on app registrations
Terraform (azuread)WS-Fed federation to Microsoft 365
Managed domain via Staged Rollout
Graph or PowerShell, under privileged activation and change approval.
Runbook
| Okta object | Entra ID target | Managed by |
|---|---|---|
| SAML and OIDC applications | Enterprise applications and app registrations | Terraform (azuread) |
| SAML attribute statements | Custom claims policy per applicationBeta Graph API; the portal edit is otherwise invisible to code. | Graph (msgraph) |
| Groups and group rules | Existing AD-synced groups; dynamic groupsMatched by lookup, not recreated. | Terraform (azuread) |
| Sign-on and authentication policies | Conditional Access policies, report-only first | Terraform (azuread) |
| Network zones | Named locations | Terraform (azuread) |
| Authenticators and enrollment | Authentication methods policy and strengthsStrengths in azuread; the methods policy needs Graph. | Graph (msgraph) |
| Admin roles | Directory roles under Privileged Identity ManagementEligible, never standing. | Terraform (azuread) |
| Authorization server scopes | Exposed API scopes on app registrations | Terraform (azuread) |
| WS-Fed federation to Microsoft 365 | Managed domain via Staged RolloutGraph or PowerShell, under privileged activation and change approval. | Runbook |
Terraform versus Graph
Terraform is the declarative engine for both platforms: it reads Okta and writes Entra ID. On the Entra ID side there are two providers, and the choice between them is the single most consequential design decision in the translation layer.
The lab settled the claims question by experiment. Each application's claims were edited in the portal and then planned. Under azuread's claims mapping policy, the portal edit was blocked and the portal displayed the wrong claims. Under the msgraph custom claims policy, the edit was allowed and Terraform saw every field. With claims held only in the portal, Terraform saw nothing. The program puts every application's claims in code through the custom claims policy.
Terraform, through the azuread provider
The mature, typed provider. It covers the core of the migration — applications, service principals, groups, Conditional Access, named locations, authentication strengths, directory roles and PIM — with plan-time validation and a clean diff. Core objects stay here.
Microsoft Graph, through the msgraph provider
A thin layer over the Graph REST API, which Microsoft labels preview. It reaches four objects on the migration's critical path that azuread does not: domain federation, Staged Rollout, the authentication methods policy and SAML custom claims. It is used for that list only, pinned to an exact version, in its own state, with PowerShell as the fallback.
Cutover
Two switches per wave, and a rollback for each
Each wave turns on two switches, in order, from one wave file: Entra ID begins authenticating the wave's users, then Okta begins redirecting them. Forward is always the first switch then the second; rollback is the reverse. Okta is never asked to redirect a user Entra ID is not yet ready to authenticate.
- S0
Okta native
Applications trust Okta. Nothing has moved.
- S1
Staged Rollout on
The wave's users join a Staged Rollout group and authenticate to the cloud with password hash sync. Verified within 30 minutes.
- S2
Okta redirects
An Okta routing rule sends those users to the Microsoft sign-in. One flag returns every user to Okta at once.
- S3
Applications repointed
Owners switch each application's trust to Entra ID. The original Okta application is unassigned, not deleted.
- S4
Validated
Healthy for the window, failures under threshold. Okta assignment removal is scheduled as its own change.
- S5
Domain converted
After the final wave the domain becomes managed and the Okta fallback ends. Okta agents leave endpoints only now.
Endpoints move with the wave. Staged Rollout groups double as the Intune assignment target, so Authenticator, Company Portal, the platform SSO extension and compliance policy arrive on a wave's Windows, macOS and mobile devices as its users move. Okta agents are not removed until the domain is converted, so a rollback never needs a reinstall.
Beyond the license
One directory under the whole Microsoft security estate
The license is the smaller part of the argument. When the identity provider is Entra ID, the sign-in, the device, the data label and the security alert share one directory and one policy engine. Okta integrates with several of the products below through connectors; what it cannot do is make them one system, because the decision about access is taken in a different tenant from the signals that should inform it.
Conditional Access
The single policy engine every Microsoft service and every migrated application evaluates at sign-in, combining user, group, location, device compliance and application in one rule.
Entra ID P1
Entra ID Protection
Scores sign-in and user risk from Microsoft's threat intelligence and feeds that score straight into Conditional Access, so a risky sign-in can require reauthentication or a password change.
Entra ID P2
Privileged Identity Management and ID Governance
Eligible rather than standing administrator roles, activated with approval and time limits; access reviews, entitlement packages and lifecycle workflows on the same objects.
Entra ID P2; ID Governance
Intune
Device compliance becomes a Conditional Access condition without an integration to maintain, replacing Okta device assurance with the management platform already deployed.
Microsoft 365 E3 / E5
Microsoft Defender XDR
Defender for Identity watches Active Directory, Defender for Cloud Apps watches SaaS sessions, and Defender for Endpoint watches the device. Their incidents correlate against the same user object the sign-in used, and containment acts on that object directly.
Microsoft 365 E5
Microsoft Sentinel
Sign-in, audit and risk logs arrive through a native connector, alongside the pipeline's own audit trail, so an investigation can follow a change from pull request to sign-in.
Consumption
Microsoft Purview
Sensitivity labels, data loss prevention and insider risk act on the identity Entra ID issued, and insider risk can raise a user's risk level in Conditional Access.
Microsoft 365 E5
Global Secure Access
Entra Private Access and Internet Access extend the same Conditional Access policy to private applications and web traffic, the path away from VPN-based access.
Entra Suite
License inclusions are Microsoft's published packaging as of October 2026. Packaging changes; confirm against the organization's agreement.
Proven in the lab
What the method has already done
The method was run end to end in a public lab before it was put in front of the client: a free Okta org seeded with fictional users, groups, applications and policies; an Entra ID tenant with a hand-built brownfield configuration; and the full pipeline. These results are observed, and each is recorded with its evidence in the lab's findings log. The repository is public.
5
Applications translated and signed in
Four SAML and one OIDC application rebuilt in Entra ID as code and tested through My Apps and an OIDC debugger.
2 of 2
Unsafe changes blocked
A rule-breaking pull request and one with no change ticket both failed the pipeline before reaching a reviewer.
1 → 1
Drift caught, one issue opened
An induced console change opened exactly one issue and was put back through the pipeline.
Lessons
What the lab changed in the plan
The lab surfaced the details a design document misses. Each of these is now a step in the client runbooks.
Read every hand-built policy back before importing it
Two of three hand-built Conditional Access policies in the lab were missing the break-glass exclusion. A brownfield import faithfully preserves that mistake, which is why the exclusion is a policy-as-code rule rather than a checklist item.
Claims edited in the portal are invisible unless they are in code
The portal can also display the wrong claims. Every application's claims belong in the repository, and any portal touch is followed by a review of the plan.
The identifier an application receives changes on migration
Okta login names that are not valid Entra ID user names change the NameID sent to the application. Application owners are told before their wave, not after it.
Give each SAML application its own identifier
Okta tolerates applications sharing one audience; Entra ID requires a unique identifier on a verified domain. The mapping catalog records the new value per application.
Budget for the gaps, and for Entra ID's replication delay
The azuread provider could not activate a SAML signing certificate, so Graph does it. Entra ID sometimes returns not-found on an object it has just created, so the pipeline retries. Neither is a defect in the method; both are work to plan for.
Size the waves to Staged Rollout's limits
Staged Rollout accepts at most ten groups per feature, security groups only, with no nesting. The wave design adds users to a fixed set of cloud groups rather than creating a group per wave.
