Provisioning Migration: Okta to Entra ID¶
Status: Authored 2026-04-30 Audience: Identity Architects, IAM Engineers, HR Systems Administrators Purpose: Detailed guidance for migrating user provisioning from Okta to Entra ID provisioning service
Overview¶
Okta provisioning automates user lifecycle management across SaaS applications -- creating accounts when users join, updating attributes when roles change, and deactivating accounts when users leave. Entra ID provisioning service provides equivalent SCIM-based provisioning with native integration into the Microsoft ecosystem.
This guide covers migration of SCIM provisioning connectors, HR-driven inbound provisioning (Workday, SuccessFactors), attribute mapping, group synchronization, and deprovisioning policies.
Microsoft has published dedicated guidance: Migrate Okta sync provisioning to Entra Connect-based synchronization.
1. Provisioning inventory¶
Export Okta provisioning configuration¶
# List all Okta applications with provisioning features
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}" \
"https://${OKTA_DOMAIN}/api/v1/apps?limit=200&filter=status+eq+%22ACTIVE%22" \
| jq '[.[] | select(.features != null and (.features | length > 0)) | {
id: .id,
label: .label,
signOnMode: .signOnMode,
features: .features,
provisioningType: (
if (.features | contains(["PUSH_NEW_USERS"])) then "Outbound (Create)"
elif (.features | contains(["IMPORT_NEW_USERS"])) then "Inbound (Import)"
else "Profile sync only" end
)
}]' > okta-provisioning-inventory.json
Categorize provisioning connectors¶
| Category | Description | Migration approach |
|---|---|---|
| SCIM outbound | Okta pushes users/groups to SaaS apps (Salesforce, ServiceNow, Slack) | Migrate to Entra provisioning service (SCIM) |
| HR inbound | HR system (Workday, SuccessFactors) provisions users into Okta | Migrate to Entra HR provisioning connectors |
| Profile sync | Attribute synchronization between Okta and apps | Configure attribute mapping in Entra provisioning |
| Group push | Group membership pushed to downstream apps | Configure group-based assignment in Entra |
| Custom provisioning | Okta Workflows or custom API integration | Redesign using Logic Apps or Lifecycle Workflows |
2. SCIM connector migration¶
Migration steps for each SCIM connector¶
For each application with SCIM provisioning in Okta:
# Step 1: Identify the Entra Enterprise Application
$app = Get-MgServicePrincipal -Filter "displayName eq 'Salesforce'"
# Step 2: Enable provisioning
$provisioningConfig = @{
provisioningMode = "automatic"
}
# Note: Provisioning configuration is typically done via the Entra admin center
# The Graph API for provisioning is under the /servicePrincipals/{id}/synchronization namespace
# Step 3: Configure provisioning credentials
# Navigate to Entra admin center > Enterprise Applications > {app} > Provisioning
# Enter SCIM endpoint URL and authentication token from the target application
# These are the same credentials used in Okta provisioning configuration
# Step 4: Configure attribute mapping (see section 3)
# Step 5: Test provisioning
# Entra admin center > Provisioning > Provision on demand
# Select a test user and verify provisioning succeeds
# Step 6: Start provisioning
# Set provisioning mode from "manual" to "automatic"
# Initial cycle runs; subsequent incremental cycles run every 40 minutes
Okta-to-Entra provisioning mapping¶
| Okta provisioning concept | Entra provisioning equivalent |
|---|---|
| SCIM base URL | Tenant URL (same SCIM endpoint) |
| API token / OAuth credentials | Secret Token or OAuth (same credentials from target app) |
| Provisioning to App | Provisioning mode: Automatic |
| Profile Master | Source of authority (attribute mapping direction) |
| Attribute mapping | Attribute mapping (Entra expression language) |
| Push Groups | Scope: Sync assigned users and groups |
| Import Users | Inbound provisioning (HR connectors) |
| Provisioning Rules | Scoping filters |
| Deactivate Users | Deprovisioning action: Disable / Delete |
3. Attribute mapping migration¶
Okta attribute mapping to Entra attribute mapping¶
| Okta expression | Entra expression | Purpose |
|---|---|---|
user.firstName | [givenName] | First name |
user.lastName | [surname] | Last name |
user.email | [mail] or [userPrincipalName] | Email address |
user.login | [userPrincipalName] | Login identifier |
user.department | [department] | Department |
user.title | [jobTitle] | Job title |
user.manager | [manager] | Manager reference |
user.employeeNumber | [employeeId] | Employee ID |
String.substringAfter(user.email, "@") | Split([mail], "@")[1] | Extract email domain |
user.status == "ACTIVE" ? true : false | IIF([accountEnabled] = "True", "Active", "Inactive") | Status conversion |
String.toUpperCase(user.department) | ToUpper([department]) | Uppercase conversion |
| Custom Okta attribute | Extension attribute or custom mapping | Map to Entra extension attributes |
Complex attribute mapping examples¶
# Entra provisioning expression: Generate display name
Join(" ", [givenName], [surname])
# Entra provisioning expression: Conditional department mapping
Switch([department],
"Unknown",
"ENG", "Engineering",
"MKT", "Marketing",
"FIN", "Finance",
"HR", "Human Resources")
# Entra provisioning expression: Extract employee type
IIF(IsPresent([employeeType]),
Switch([employeeType], "Employee", "C", "Contractor", "V", "Vendor"),
"Employee")
# Entra provisioning expression: Generate SAMAccountName from UPN
Left(Split([userPrincipalName], "@")[0], 20)
4. HR-driven provisioning migration¶
Workday inbound provisioning¶
Okta's Workday integration provisions users from Workday into Okta's Universal Directory. Entra's native Workday connector provisions users directly into Entra ID (or on-premises AD via Cloud Sync).
# Configure Workday inbound provisioning
# This is configured via the Entra admin center:
# Enterprise Applications > Workday to Azure AD User Provisioning
# Key configuration steps:
# 1. Provide Workday API credentials (ISU account)
# 2. Map Workday worker attributes to Entra user attributes
# 3. Configure matching rules (Worker ID -> employeeId)
# 4. Set scoping filters (active workers, specific supervisory orgs)
# 5. Test with provision-on-demand
# 6. Enable automatic provisioning
Workday attribute mapping¶
| Workday attribute | Okta mapping | Entra mapping |
|---|---|---|
| Worker ID | profile.employeeNumber | employeeId |
| Legal First Name | profile.firstName | givenName |
| Legal Last Name | profile.lastName | surname |
| Email Address | profile.email | mail |
| Supervisory Org | profile.department | department |
| Job Title | profile.title | jobTitle |
| Manager Worker ID | profile.manager | manager (via lookup) |
| Location | profile.city, profile.state | city, state |
| Cost Center | profile.costCenter | companyName or extension attribute |
| Worker Status | profile.status (active/inactive) | accountEnabled (true/false) |
SuccessFactors inbound provisioning¶
# Configure SuccessFactors inbound provisioning
# Enterprise Applications > SAP SuccessFactors to Azure AD User Provisioning
# Similar configuration pattern to Workday:
# 1. Provide SuccessFactors API credentials
# 2. Map Employee Central attributes to Entra user attributes
# 3. Configure matching rules
# 4. Set scoping filters
# 5. Test and enable
5. Group provisioning¶
Okta group push to Entra group-based provisioning¶
Okta's "Push Groups" feature pushes group membership to downstream applications. Entra's equivalent uses group-based application assignment with provisioning.
# Step 1: Create Entra security groups matching Okta groups
$groups = @(
@{ DisplayName = "Salesforce-Users"; Description = "Users provisioned to Salesforce" },
@{ DisplayName = "ServiceNow-Users"; Description = "Users provisioned to ServiceNow" },
@{ DisplayName = "Slack-Users"; Description = "Users provisioned to Slack" }
)
foreach ($group in $groups) {
New-MgGroup -DisplayName $group.DisplayName `
-Description $group.Description `
-MailEnabled:$false `
-MailNickname ($group.DisplayName -replace "[^a-zA-Z0-9]", "") `
-SecurityEnabled:$true
}
# Step 2: Assign groups to enterprise applications
$salesforceApp = Get-MgServicePrincipal -Filter "displayName eq 'Salesforce'"
$salesforceGroup = Get-MgGroup -Filter "displayName eq 'Salesforce-Users'"
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $salesforceApp.Id -Body @{
principalId = $salesforceGroup.Id
resourceId = $salesforceApp.Id
appRoleId = "00000000-0000-0000-0000-000000000000" # Default access role
}
# Step 3: Configure provisioning scope to "Sync assigned users and groups"
# This ensures only members of the assigned group are provisioned
Dynamic groups (replacing Okta group rules)¶
# Create dynamic group equivalent to Okta group rule
# Okta rule: user.department == "Engineering" AND user.status == "ACTIVE"
New-MgGroup -DisplayName "Engineering-Dynamic" `
-Description "All active Engineering department users" `
-MailEnabled:$false `
-MailNickname "EngineeringDynamic" `
-SecurityEnabled:$true `
-GroupTypes @("DynamicMembership") `
-MembershipRule "(user.department -eq `"Engineering`") and (user.accountEnabled -eq true)" `
-MembershipRuleProcessingState "On"
6. Deprovisioning policies¶
Okta deprovisioning to Entra deprovisioning¶
| Okta deprovisioning action | Entra provisioning equivalent | Configuration |
|---|---|---|
| Deactivate user in app | Disable user in app (soft delete) | Deprovisioning: Disable |
| Remove user from app | Delete user in app (hard delete) | Deprovisioning: Delete |
| Do nothing | Skip deprovisioning | No action on unassignment |
| Suspend user | Disable user (platform-dependent) | Disable with flag |
# Configure deprovisioning behavior
# In Entra admin center > Enterprise Applications > {app} > Provisioning > Settings
# Options for "When a user falls out of scope":
# 1. "Disable" - Sends SCIM PATCH to set active=false (recommended)
# 2. "Delete" - Sends SCIM DELETE to remove the user (use with caution)
# Accidental deletion prevention
# Entra provisioning includes a deletion threshold (default: 500)
# If more than threshold users would be deleted in a cycle, provisioning pauses
# This prevents mass deletion from misconfigured scoping filters
7. Lifecycle Workflows (replacing Okta lifecycle automation)¶
Entra ID Governance Lifecycle Workflows replace Okta's lifecycle management automation:
# Create a Joiner workflow (equivalent to Okta's onboarding automation)
$joinerWorkflow = @{
displayName = "New Employee Onboarding"
description = "Automated onboarding for new employees"
isEnabled = $true
isSchedulingEnabled = $true
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "department eq 'Engineering'"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeHireDate"
offsetInDays = 0 # On hire date
}
}
tasks = @(
@{
displayName = "Generate TAP"
taskDefinitionId = "1b555e50-7f65-41d5-b514-5894a026d10d"
isEnabled = $true
arguments = @(
@{ name = "tapLifetimeMinutes"; value = "480" }
@{ name = "tapIsUsableOnce"; value = "true" }
)
},
@{
displayName = "Add to Engineering group"
taskDefinitionId = "22085229-5809-45e8-97fd-270d28d66910"
isEnabled = $true
arguments = @(
@{ name = "groupID"; value = "engineering-group-id" }
)
},
@{
displayName = "Enable user account"
taskDefinitionId = "6fc52c9d-398b-4305-9763-15f42c1676fc"
isEnabled = $true
},
@{
displayName = "Send welcome email"
taskDefinitionId = "70b29d51-b59a-4773-9280-8841dfd3f2ea"
isEnabled = $true
}
)
}
New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $joinerWorkflow
Leaver workflow¶
# Create a Leaver workflow (equivalent to Okta's offboarding automation)
$leaverWorkflow = @{
displayName = "Employee Offboarding"
description = "Automated offboarding when employees leave"
isEnabled = $true
isSchedulingEnabled = $true
executionConditions = @{
"@odata.type" = "#microsoft.graph.identityGovernance.triggerAndScopeBasedConditions"
scope = @{
"@odata.type" = "#microsoft.graph.identityGovernance.ruleBasedSubjectSet"
rule = "department ne null"
}
trigger = @{
"@odata.type" = "#microsoft.graph.identityGovernance.timeBasedAttributeTrigger"
timeBasedAttribute = "employeeLeaveDateTime"
offsetInDays = 0 # On leave date
}
}
tasks = @(
@{
displayName = "Disable user account"
taskDefinitionId = "1dfdfcc7-52fa-4c2e-bf3a-e3919cc12950"
isEnabled = $true
},
@{
displayName = "Remove from all groups"
taskDefinitionId = "b3a31406-2a15-4c9a-b25b-a658fa5f07fc"
isEnabled = $true
},
@{
displayName = "Revoke all sign-in sessions"
taskDefinitionId = "8a0b7d16-3e0f-4e82-b6d0-c5bfb41e5b6b"
isEnabled = $true
},
@{
displayName = "Remove access to all applications"
taskDefinitionId = "4a0b64f2-c7ec-46ba-b117-18f262946c50"
isEnabled = $true
}
)
}
New-MgIdentityGovernanceLifecycleWorkflow -BodyParameter $leaverWorkflow
8. Provisioning monitoring and troubleshooting¶
# Check provisioning status for an application
$sp = Get-MgServicePrincipal -Filter "displayName eq 'Salesforce'"
$provStatus = Get-MgServicePrincipalSynchronizationJob -ServicePrincipalId $sp.Id
$provStatus | ForEach-Object {
Write-Host "Job ID: $($_.Id)"
Write-Host "Status: $($_.Status.Code)"
Write-Host "Last execution: $($_.Status.LastExecution.ActivityIdentifier)"
Write-Host "Last successful: $($_.Status.LastSuccessfulExecution.TimeBegan)"
Write-Host "Quarantine status: $($_.Status.QuarantineStatus.CurrentBegan)"
}
# View provisioning logs
$provLogs = Get-MgAuditLogProvisioning -Filter "servicePrincipal/id eq '$($sp.Id)'" -Top 50
$provLogs | ForEach-Object {
Write-Host "$($_.ActivityDateTime): $($_.Action) - $($_.ProvisioningStatusInfo.Status) - $($_.TargetIdentity.DisplayName)"
}
Common provisioning issues¶
| Issue | Cause | Resolution |
|---|---|---|
| Provisioning in quarantine | Authentication failure to target app | Verify SCIM credentials; re-authorize if using OAuth |
| Attribute mapping error | Expression syntax error or missing source attribute | Review attribute mapping expressions; test with provision-on-demand |
| Duplicate user | Matching rule not finding existing user | Verify matching attribute (email, employeeId) exists in target |
| Group not provisioning | Group not assigned to application | Assign group to enterprise application; set scope to "assigned users and groups" |
| Slow initial cycle | Large user count (10,000+) | Initial cycles can take hours; incremental cycles are faster (40-min intervals) |
Key Microsoft Learn references¶
- Entra ID provisioning service
- Attribute mapping expressions
- Workday inbound provisioning
- SuccessFactors inbound provisioning
- Lifecycle Workflows
- Provision on demand
Maintainers: csa-inabox core team Last updated: 2026-04-30