In this module
IAM0.2 The Identity Lifecycle in Your Tenant
IAM0.1 established the distinction between administering identity and governing it — and made it concrete with the governance attribute coverage audit, the group membership trace, and the stale identity analysis. This section traces the five-stage identity lifecycle through your own Graph API data, showing where each stage is governed, where it's unmanaged, and where the gaps produce the failures IAM0.1 surfaced.
The lifecycle is not three events — it's five continuous stages
Most organizations think of identity in three events: someone joins (create an account), someone leaves (disable the account), and maybe — if the process exists — someone changes roles (update the title field). That's the joiner-mover-leaver model, and it captures the administrative events. It misses the governance stages entirely.
The identity lifecycle has five stages, and the three events are only the start. Creation establishes the identity. Assignment grants it access. Governance verifies the access is still appropriate. Monitoring detects anomalies and staleness. Removal revokes access and decommissions the identity. The joiner is stage 1. The mover touches stages 2 and 3. The leaver is stage 5. Stages 3 and 4 — governance and monitoring — are the stages most tenants skip entirely, and they're the stages where access accumulates, stale identities persist, and audit failures originate.
In this section you'll trace each lifecycle stage through your own Graph API data. You'll see where your tenant has a governed lifecycle and where identities pass through stages without governance controls. By the end, you'll have a lifecycle stage map for your tenant showing which stages have automation, which have manual processes, and which have nothing at all.
Estimated time: 55 minutes.
Figure IAM0.2 — The five-stage identity lifecycle. Most tenants manage creation and removal (partially). Assignment is ad-hoc. Governance and monitoring — the stages that prevent access accumulation — are absent in most environments. The governance loop (dashed) connects stage 3 back to stage 2: a review finding triggers access reassignment.
Stage 1 — Creation: where did this identity come from?
Every identity in your tenant was created through one of four channels: manual creation in the admin center, HR-driven provisioning (inbound from Workday, SuccessFactors, or a CSV import), invitation (guest accounts), or programmatic creation (service principals, managed identities, application registrations). The channel determines the governance baseline — an HR-driven identity arrives with populated attributes because the HR system provides them. A manually created identity has whatever attributes the admin typed in.
Entra Admin Center
Identity → Users → All users → sort by Created date (click the column header)
The most recently created accounts appear at the top. Click any recent user → Properties → Job info. Check whether Employee hire date is populated. If it is, the account was likely created through an HR-driven or deliberate process. If it's blank, the account was created manually without governance attributes. Compare 3–4 recent accounts this way — the pattern of populated vs empty hire dates tells you whether your creation process produces governed identities or administrative ones.
Query your tenant's identity creation patterns programmatically:
$recentUsers = Get-MgUser -All -Property displayName, createdDateTime,
creationType, userType, employeeHireDate, department, manager |
Where-Object { $_.CreatedDateTime -gt (Get-Date).AddMonths(-6) }
Write-Host "Identities created in the last 6 months: $($recentUsers.Count)"
$recentUsers | ForEach-Object {
$hasMgr = [bool](Get-MgUserManager -UserId $_.Id -ErrorAction SilentlyContinue)
[PSCustomObject]@{
Name = $_.DisplayName
Type = $_.UserType
Created = $_.CreatedDateTime.ToString("yyyy-MM-dd")
Source = if ($_.CreationType -eq "Invitation") { "Invitation" }
elseif ($_.EmployeeHireDate) { "HR-driven (has hire date)" }
else { "Manual (no hire date)" }
Department = if ($_.Department) { $_.Department } else { "(NONE)" }
HasManager = $hasMgr
}
} | Format-Table -AutoSizeIdentities created in the last 6 months: 47
Name Type Created Source Department HasManager
---- ---- ------- ------ ---------- ----------
Tom Ashworth Member 2026-01-15 Manual (no hire date) Security True
Guest - Contoso Guest 2026-02-03 Invitation (NONE) False
svc-api-gateway Member 2026-02-18 Manual (no hire date) (NONE) False
Elena Petrova Member 2026-03-01 HR-driven (has hire date) GRC True
New Contractor Member 2026-03-20 Manual (no hire date) (NONE) False
...The Source column reveals governance gaps at the point of creation. Tom Ashworth was created manually with no hire date — lifecycle workflows that trigger on employeeHireDate won't fire for him. He has a manager assigned, so manager-based access reviews will work. svc-api-gateway was created as a member account (not a service principal) with no department and no manager — it's a service account masquerading as a person, invisible to any governance process that targets human attributes.
Elena Petrova was created through HR-driven provisioning — she has a hire date, department, and manager. Lifecycle workflows can fire for her. Dynamic groups based on department will evaluate her. Manager-based reviews will route to her manager. The provisioning channel created the governance foundation.
The governance question for stage 1: does the creation channel populate the attributes that downstream governance depends on? If 80% of your identities are created manually with no hire date (the IAM0.1 finding), then 80% of your lifecycle automation is broken at the source. The workflow isn't misconfigured — the data it depends on was never provided.
At Northgate Engineering: Phil Greaves, the IT Director, creates all accounts manually through the admin center. He populates displayName, UPN, and department — but not employeeHireDate, because the admin center doesn't require it and nobody told him lifecycle workflows depend on it. HR uses a standalone system with no Entra ID integration. The governance gap isn't a workflow problem. It's a provisioning source problem. Module 2 builds the solution.
Stage 2 — Assignment: how did it get this access?
Once an identity exists, it receives access through group memberships, role assignments, application permissions, and entitlement management packages. In most tenants, the assignment stage is entirely ad-hoc — someone adds a user to a group during onboarding, another group gets added during a project, a third when a colleague shares a Teams channel. There's no record of why, no approval trail, and no expiry.
You traced a single identity's group memberships in IAM0.1. Now aggregate across the tenant to see the assignment landscape:
$groups = Get-MgGroup -All -Property id, displayName, groupTypes,
securityEnabled, createdDateTime, description
$ownerless = @()
$noDesc = @()
foreach ($g in $groups) {
$ownerCount = (Get-MgGroupOwner -GroupId $g.Id -ErrorAction SilentlyContinue).Count
if ($ownerCount -eq 0) { $ownerless += $g }
if (-not $g.Description) { $noDesc += $g }
}
$dynamic = ($groups | Where-Object { $_.GroupTypes -contains "DynamicMembership" }).Count
Write-Host "=== GROUP LANDSCAPE ==="
Write-Host "Total groups: $($groups.Count)"
Write-Host "Ownerless: $($ownerless.Count) ($([math]::Round($ownerless.Count/$groups.Count*100))%)"
Write-Host "No description: $($noDesc.Count) ($([math]::Round($noDesc.Count/$groups.Count*100))%)"
Write-Host "Dynamic: $dynamic ($([math]::Round($dynamic/$groups.Count*100))%)"=== GROUP LANDSCAPE ===
Total groups: 214
Ownerless: 67 (31%)
No description: 89 (42%)
Dynamic: 12 (6%)Read the numbers. 31% of groups have no owner — nobody is accountable for who's in them. 42% have no description — nobody documented what access the group grants or why it exists. Only 6% are dynamic — the rest depend on someone manually adding and removing members. In a governed lifecycle, group membership changes automatically when an identity's attributes change (department transfer → old department group removed, new department group added). In an administered lifecycle, group membership changes when someone remembers to update it.
The 12 dynamic groups are the only groups where stage 2 is automated. The other 202 groups depend on manual assignment. When Priya Sharma transferred from finance to SOC operations, someone added her to the SOC groups. Nobody removed her from the finance groups. That's stage 2 without governance — access granted, never reassessed.
At Northgate Engineering: The group landscape audit returns 214 groups, 67 ownerless, 89 without descriptions, and 12 dynamic. Rachel Okafor traces the 67 ownerless groups and finds they collectively grant access to 47 SharePoint sites, 23 Teams channels, and 12 application permissions. That's access governed by nobody. When she runs an access review scoped to those groups, the review can't assign reviewers because there are no owners. The groups represent governed-by-default access — whoever was added stays added until someone notices. Nobody notices.
Stage 3 — Governance: is this access still justified?
Governance is the stage where access is reviewed, certified, or revoked. In Entra ID, the primary governance mechanism is access reviews — scheduled reviews that route to managers or resource owners, asking them to confirm or deny that each identity still needs the access it holds.
Entra Admin Center
Identity Governance → Access reviews
If this page loads and shows review definitions, your tenant has access reviews configured. If the page is empty, Stage 3 doesn't exist — no review mechanism is in place. If the menu item shows a license error, you need P2 (basic reviews) or the Governance add-on (advanced reviews). The absence of reviews is itself the finding: every access decision made in Stage 2 persists indefinitely with no certification mechanism.
Check whether your tenant has any active access reviews programmatically:
Connect-MgGraph -Scopes "AccessReview.Read.All"
$reviews = Get-MgIdentityGovernanceAccessReviewDefinition -All -ErrorAction SilentlyContinue
if ($reviews) {
Write-Host "Active access review definitions: $($reviews.Count)"
$reviews | Select-Object displayName, status,
@{N='Scope'; E={$_.Scope.Query}},
@{N='Reviewers'; E={($_.Reviewers | ForEach-Object { $_.Query }) -join ", "}},
@{N='Recurrence'; E={$_.Settings.Recurrence.Pattern.Type}} |
Format-Table -AutoSize
} else {
Write-Host "No access reviews configured. Stage 3 is absent."
}Three common results:
No reviews configured. Stage 3 doesn't exist. Every access decision made in stage 2 persists indefinitely. The finance group membership from 2022 is still active. The project group from a completed initiative still grants access. Nobody reviews anything because no review mechanism exists. This is the most common production state.
Reviews exist but incomplete. The tenant has access reviews, but they cover a subset of groups or roles. Privileged roles might be reviewed quarterly while standard group memberships are never reviewed. Guest accounts might have expiry policies while member access reviews don't exist.
Reviews exist with completion problems. Reviews are configured and run on schedule, but reviewers click "Approve All" in under 60 seconds. The review produces compliance evidence — a completion report showing 100% of items reviewed — while providing zero governance value. The access was "reviewed" in the same way a CAPTCHA is "read." Module 5 addresses this directly.
If your tenant has reviews, check their completion quality:
$reviews | ForEach-Object {
$instances = Get-MgIdentityGovernanceAccessReviewDefinitionInstance `
-AccessReviewScheduleDefinitionId $_.Id -All
$instances | ForEach-Object {
$decisions = Get-MgIdentityGovernanceAccessReviewDefinitionInstanceDecision `
-AccessReviewScheduleDefinitionId $_.Id `
-AccessReviewInstanceId $_.Id -All
$total = $decisions.Count
$approved = ($decisions | Where-Object { $_.Decision -eq "Approve" }).Count
$denied = ($decisions | Where-Object { $_.Decision -eq "Deny" }).Count
[PSCustomObject]@{
Review = $_.DisplayName
Total = $total
Approved = $approved
Denied = $denied
ApproveRate = if ($total -gt 0) { "$([math]::Round($approved/$total*100))%" } else { "N/A" }
}
}
} | Format-Table -AutoSizeIf the approve rate is 100% across every review, governance stage 3 technically exists but functionally doesn't. A review that approves everything is a rubber stamp.
Stage 4 — Monitoring: is this identity still active?
Monitoring detects identities that have gone stale — accounts that haven't signed in, credentials approaching expiry, anomalous access patterns. You ran the stale identity query in IAM0.1. Now look at the monitoring stage more broadly by examining the signInActivity data:
$signInDistribution = $members | Where-Object { $_.AccountEnabled -eq $true } |
ForEach-Object {
$lastSign = $_.SignInActivity.LastSignInDateTime
if (-not $lastSign) { "Never signed in" }
elseif ($lastSign -gt (Get-Date).AddDays(-30)) { "Active (30d)" }
elseif ($lastSign -gt (Get-Date).AddDays(-90)) { "Inactive (30-90d)" }
elseif ($lastSign -gt (Get-Date).AddDays(-180)) { "Stale (90-180d)" }
else { "Dormant (180d+)" }
} | Group-Object | Sort-Object Count -Descending
Write-Host "=== MEMBER SIGN-IN DISTRIBUTION ==="
$signInDistribution | ForEach-Object {
Write-Host " $($_.Name): $($_.Count)"
}=== MEMBER SIGN-IN DISTRIBUTION ===
Active (30d): 691
Inactive (30-90d): 57
Stale (90-180d): 31
Never signed in: 17
Dormant (180d+): 14The distribution reveals the monitoring gap. 691 active accounts are functioning normally. But 119 accounts (57 + 31 + 17 + 14) have varying degrees of staleness, and 17 enabled accounts have never signed in at all. In a governed lifecycle, each category triggers a different response. Inactive accounts (30-90 days) get a notification to the manager. Stale accounts (90-180 days) get flagged for review. Dormant accounts (180+ days) get disabled pending investigation. Never-signed-in accounts get escalated to the provisioning team — the account was created but the identity never used it, which means either the person never started, the account was created in error, or the account was created for a purpose that doesn't require interactive sign-in.
Without monitoring, all 119 accounts persist unchanged. The access they hold remains active. The groups they belong to continue granting permissions. A dormant account with Global Administrator is as dangerous as an active one — more dangerous, because nobody is watching it.
Stage 5 — Removal: clean decommission or residual access?
The final lifecycle stage is where the identity is disabled and its access is revoked.
The leaver process is: HR emails IT → IT disables the account → done. Nobody removes group memberships, role assignments, or application permissions. The account is disabled but holds the same access it had on its last working day. If re-enabled — through admin error, social engineering, or a legitimate rehire — every permission reactivates instantly. This isn't decommissioning. It's suspending with full access preservation.
Entra Admin Center
Identity → Users → All users → Add filter → Account enabled → No
This filters to disabled accounts only. Click any disabled account → Groups. If the group list is not empty, the disabled account still holds group memberships — and the access those groups grant. The account can't exercise the access while disabled, but the permissions persist. Check 3–4 disabled accounts this way. Count how many still have group memberships. That count is the residual access problem.
Query your tenant's disabled accounts to see whether removal was clean:
$disabledWithGroups = $disabled | ForEach-Object {
$groupCount = (Get-MgUserMemberOf -UserId $_.Id -All -ErrorAction SilentlyContinue).Count
[PSCustomObject]@{
Name = $_.DisplayName
UPN = $_.UserPrincipalName
Disabled = $_.AccountEnabled
Groups = $groupCount
LastSignIn = if ($_.SignInActivity.LastSignInDateTime) {
$_.SignInActivity.LastSignInDateTime.ToString("yyyy-MM-dd")
} else { "Unknown" }
}
} | Where-Object { $_.Groups -gt 0 }
Write-Host "Disabled accounts still in groups: $($disabledWithGroups.Count) / $($disabled.Count)"
$disabledWithGroups | Format-Table -AutoSizeDisabled accounts still in groups: 11 / 14
Name UPN Groups LastSignIn
---- --- ------ ----------
James Whitfield j.whitfield@yourtenant.com 7 2025-11-14
Former Contractor contractor1@yourtenant.com 3 2025-08-20
svc-old-backup svc-old-backup@yourtenant.com 2 2025-06-03
...Eleven of 14 disabled accounts still hold group memberships. The accounts are disabled — they can't sign in — but the group memberships remain. Those memberships represent residual access. If a disabled account is re-enabled (for investigation, for a contractor returning, for an error), it immediately regains every group membership it held at the time of disabling. The account wasn't decommissioned. It was paused.
A governed removal stage follows a sequence: disable the account, revoke active sessions, remove all group memberships, remove role assignments, remove application permissions, convert the mailbox to shared (if needed for business continuity), and after a retention period, delete the account. An administered removal stage disables the account and stops. The access persists in suspended form.
At Northgate Engineering: When Marcus Webb, the Security Architect, investigates a former contractor's account, he finds it was disabled three months ago but still holds membership in SG-Engineering, SG-Project-Alpha, and Team-Vendor-Onboarding-2024. If the account were re-enabled — by an admin error, a social engineering attack on the helpdesk, or a legitimate request from the contractor's company — it would immediately have access to engineering resources, the project SharePoint site, and the vendor onboarding Teams channel. The disable action looked like removal. It was actually suspension with full access preservation.
Your lifecycle stage map
You've now traced all five stages through your own tenant data. Before you move on, document what you found. This becomes the baseline assessment you'll improve across the course.
For each stage, record the current state: Automated (a governance mechanism handles it without manual intervention), Manual (someone does it but there's no automation or enforcement), Partial (some identities or scenarios are covered, others aren't), or Absent (no process exists).
Your findings from this section and IAM0.1 give you the data. The identity census and attribute coverage audit map creation. The group landscape and membership trace map assignment. The access review query maps governance. The sign-in distribution maps monitoring. The disabled-account-with-groups query maps removal. When you reach IAM0.7, this lifecycle stage map becomes the first artifact in your IAM program package.
Reusable script — the lifecycle stage diagnostic commands from this section assembled for operational use:
# IAM0.2 — Identity Lifecycle Stage Diagnostic
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All",
"GroupMember.Read.All", "AccessReview.Read.All", "AuditLog.Read.All"
# Stage 1 — Creation: provisioning source analysis (last 6 months)
$recentUsers = Get-MgUser -All -Property displayName, createdDateTime,
creationType, userType, employeeHireDate, department |
Where-Object { $_.CreatedDateTime -gt (Get-Date).AddMonths(-6) }
$hrDriven = ($recentUsers | Where-Object { $_.EmployeeHireDate }).Count
$manual = ($recentUsers | Where-Object { $_.UserType -eq "Member" -and -not $_.EmployeeHireDate }).Count
$invited = ($recentUsers | Where-Object { $_.CreationType -eq "Invitation" }).Count
Write-Host "=== STAGE 1: CREATION (last 6 months) ==="
Write-Host "Total created: $($recentUsers.Count)"
Write-Host " HR-driven (has hire date): $hrDriven"
Write-Host " Manual (no hire date): $manual"
Write-Host " Guest invitation: $invited"
# Stage 2 — Assignment: group governance state
$groups = Get-MgGroup -All -Property id, displayName, groupTypes, description
$ownerless = 0
foreach ($g in $groups) {
if ((Get-MgGroupOwner -GroupId $g.Id -ErrorAction SilentlyContinue).Count -eq 0) {
$ownerless++
}
}
$noDesc = ($groups | Where-Object { -not $_.Description }).Count
$dynamic = ($groups | Where-Object { $_.GroupTypes -contains "DynamicMembership" }).Count
Write-Host "`n=== STAGE 2: ASSIGNMENT ==="
Write-Host "Total groups: $($groups.Count)"
Write-Host " Ownerless: $ownerless ($([math]::Round($ownerless/$groups.Count*100))%)"
Write-Host " No description: $noDesc ($([math]::Round($noDesc/$groups.Count*100))%)"
Write-Host " Dynamic: $dynamic ($([math]::Round($dynamic/$groups.Count*100))%)"
# Stage 3 — Governance: access review state
$reviews = Get-MgIdentityGovernanceAccessReviewDefinition -All -ErrorAction SilentlyContinue
Write-Host "`n=== STAGE 3: GOVERNANCE ==="
if ($reviews) {
Write-Host "Access review definitions: $($reviews.Count)"
$reviews | Select-Object displayName, status | Format-Table -AutoSize
} else {
Write-Host "No access reviews configured. Stage 3: ABSENT."
}
# Stage 4 — Monitoring: sign-in distribution
$users = Get-MgUser -All -Property accountEnabled, userType, signInActivity
$activeMembers = $users | Where-Object {
$_.UserType -eq "Member" -and $_.AccountEnabled -eq $true
}
$active30 = ($activeMembers | Where-Object {
$_.SignInActivity.LastSignInDateTime -gt (Get-Date).AddDays(-30)
}).Count
$stale90 = ($activeMembers | Where-Object {
$_.SignInActivity.LastSignInDateTime -and
$_.SignInActivity.LastSignInDateTime -lt (Get-Date).AddDays(-90)
}).Count
$neverSignedIn = ($activeMembers | Where-Object {
-not $_.SignInActivity.LastSignInDateTime
}).Count
Write-Host "`n=== STAGE 4: MONITORING ==="
Write-Host "Active members: $($activeMembers.Count)"
Write-Host " Signed in (30d): $active30"
Write-Host " Stale (90d+): $stale90"
Write-Host " Never signed in: $neverSignedIn"
# Stage 5 — Removal: disabled with residual access
$disabled = $users | Where-Object { $_.AccountEnabled -eq $false }
$disabledInGroups = 0
foreach ($d in $disabled) {
$gc = (Get-MgUserMemberOf -UserId $d.Id -All -ErrorAction SilentlyContinue).Count
if ($gc -gt 0) { $disabledInGroups++ }
}
Write-Host "`n=== STAGE 5: REMOVAL ==="
Write-Host "Disabled accounts: $($disabled.Count)"
Write-Host " Still in groups: $disabledInGroups"Decision-point simulation
Scenario 1. Your lifecycle telemetry shows 23 users with employeeLeaveDateTime set to dates in the past — but all 23 accounts are still enabled. What does this tell you, and what's the risk?
It tells you the departure process is broken. Either HR sets the leave date but nobody acts on it, or the lifecycle workflow that should disable accounts on that date isn't configured (or isn't firing). The risk: 23 former employees may still have active credentials. Run revokeSignInSessions immediately for all 23, disable the accounts, and investigate why the lifecycle trigger failed. The root cause is almost always a data quality issue — the attribute exists but nothing reads it.
Scenario 2. A user moved from Engineering to Finance 8 months ago. Their department attribute was updated, but they still have access to the Engineering SharePoint site, the Engineering Teams channel, and an Engineering-only app registration. What lifecycle stage failed?
The mover stage. The department attribute changed (the identity data was updated), but the access assignments weren't re-evaluated. If access was assigned through dynamic groups based on department, the Engineering groups should have automatically removed the user. If access was assigned through static groups or direct assignment, no automatic removal occurs. The fix: audit whether Engineering access uses dynamic or static groups. Static group assignments need a mover review workflow in Module 12.
Scenario 3. Your identity census shows 34 guest accounts. 12 were created in the last 90 days. 22 were created more than 6 months ago. 8 of the 22 have never signed in. What lifecycle stage applies to the 8 never-signed-in guests, and what should happen to them?
The 8 never-signed-in guests are stuck in the creation stage — they were invited but never redeemed. They're PendingAcceptance objects consuming directory space with no governance value. The invitation either wasn't needed, wasn't received, or was ignored. Contact the inviter (if identifiable) to confirm whether the collaboration still applies. If not, revoke the invitation and delete the guest object. If the inviter has left the organization, delete the unredeemed invitation — if the external user genuinely needs access, they can be re-invited with a proper sponsor.
You're reading the free modules of Identity and Access Management in Microsoft 365
The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts.