The Identity Lifecycle
From first day to last: joiners, movers and leavers, HR-driven provisioning, SCIM, access requests and approvals, access reviews and recertification, and orphaned accounts.
Joiners, movers and leavers
An identity is not created once and then left alone. It follows a person through the organization, and every change in their job is a change in what they should be able to reach. The joiner-mover-leaver (JML) model names the three events that drive it. The principle is introduced in Access Control; this page shows how it is built.
Take a newly hired network engineer at Kestrel Energy, Emeka Obi:
- Joiner: HR records his start date and job role a few weeks ahead. On (or just before) day one, his account exists, he is in the groups every network engineer gets, he has a mailbox and a license, and he can register MFA with a one-time onboarding code (a Temporary Access Pass in Entra). Nobody copied a colleague's account to get there.
- Mover: a service desk analyst who becomes a network engineer gets the network engineer groups — and loses the service desk groups, including the right to read local administrator passwords. The removal is the half that is usually forgotten.
- Leaver: on the last working day the account is disabled and existing sessions are revoked; group memberships, licenses and devices are cleaned up afterward, and the account is deleted after a retention period.
HR-driven provisioning
Provisioning is creating an identity and giving it the accounts and access it needs — in the directory and in each application. The question is what triggers it. In many organizations the trigger is an email from a manager to the service desk. That works until the email is late, incomplete, or never sent, which is exactly what happens with leavers.
In HR-driven provisioning the HR system is the source of truth (the authoritative source) for who works here, in which role, from when until when. An identity system reads HR changes and acts on them:
- A new hire record creates the account, sets attributes (department, job title, manager, employee ID) and adds the birthright groups for the role — the access everyone in that role gets without asking.
- A change of department or job title updates the attributes and swaps the role groups.
- An end date disables the account at the end of the last working day.
In Microsoft Entra this is called inbound provisioning. As of 2026 there are built-in connectors for Workday and SAP SuccessFactors, and API-driven inbound provisioning lets any HR system send its records to Entra ID or on-premises AD through Microsoft Graph. Entra ID Governance adds lifecycle workflows, which run tasks such as generating a Temporary Access Pass before day one or removing groups on the last day. The employee ID that HR assigns is the key that ties every account back to a person.
Provisioning and deprovisioning to applications
The directory is only the first system. A typical employee also has accounts in a ticketing tool, a learning platform, a CRM and a dozen software-as-a-service (SaaS) applications. Single sign-on lets them sign in with one identity, but many applications still keep their own user record — and that record must be created before the first sign-in and removed after the last one.
Deprovisioning is the reverse of provisioning: disabling or deleting the accounts and removing the access of an identity that no longer needs them. It matters more than provisioning, because a missing account generates a ticket within an hour, while an account that should be gone generates nothing at all. With SSO, disabling the directory account blocks new sign-ins everywhere at once; but an application account with a local password, an API token or an active session survives unless it is deprovisioned in the application itself.
A typical leaver runbook for the directory part, in PowerShell (non-run; the ActiveDirectory module and Microsoft Graph PowerShell):
# On-premises AD: disable and move to the Leavers OU
Disable-ADAccount -Identity billing.e1003
Get-ADUser -Identity billing.e1003 |
Move-ADObject -TargetPath "OU=Leavers,DC=ad,DC=example,DC=com"
# Microsoft Entra ID: block sign-in and revoke refresh tokens
Connect-MgGraph -Scopes "User.ReadWrite.All"
Update-MgUser -UserId billing.e1003@example.com -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId billing.e1003@example.com
Revoking sign-in sessions invalidates refresh tokens, so no new access tokens are issued; an access token that was already issued can stay valid until it expires (by default 60 to 90 minutes in Entra ID), unless the application supports continuous access evaluation (see Conditional Access and Zero Trust). For a hybrid account, disabling in AD reaches Entra only after the next synchronization cycle, which is why urgent leavers are disabled in both places.
SCIM: a standard for provisioning
Before a standard existed, every application had its own provisioning API or a nightly CSV import. The SCIM (System for Cross-domain Identity Management) 2.0 standard defines one REST API, with JSON bodies, for managing users and groups. It consists of RFC 7643 (the core schema: what a user and a group look like), RFC 7644 (the protocol: endpoints and operations) and RFC 7642 (concepts and use cases), all from September 2015.
The identity provider is the SCIM client; the application is the SCIM service provider that exposes the endpoints. A new user, as the identity provider sends it to the application on Emeka's first day (non-run):
{
"schemas": [
"urn:ietf:params:scim:schemas:core:2.0:User",
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
],
"externalId": "E1004",
"userName": "emeka.obi@example.com",
"name": {
"givenName": "Emeka",
"familyName": "Obi"
},
"displayName": "Emeka Obi",
"title": "Network engineer",
"emails": [
{ "value": "emeka.obi@example.com", "type": "work", "primary": true }
],
"active": true,
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "E1004",
"department": "Network Operations"
}
}
schemas says which schemas the resource uses: the core
user and the enterprise extension (employee number, department,
manager). externalId is the client's own identifier,
and active is the switch that deprovisioning flips. The
application answers with the same resource plus its own
id and a meta block, and the client stores
that id for later updates.
| Request | Purpose |
|---|---|
POST /Users |
Create a user (joiner) |
GET /Users?filter=userName eq
"emeka.obi@example.com"
|
Find a user, for example to check whether it already exists |
GET /Users/{id} |
Read one user |
PATCH /Users/{id} |
Change some attributes (mover), or set
active to false (leaver)
|
PUT /Users/{id} |
Replace the whole user |
DELETE /Users/{id} |
Delete the user |
/Groups |
The same operations for groups and their members |
/ServiceProviderConfig, /Schemas,
/ResourceTypes
|
Discovery: what the application supports (filtering, PATCH, bulk) |
Deactivating a leaver is a small PATCH request; many provisioning
services, including Microsoft Entra's, first set
active to false and delete the user later, so that a
mistake can be undone:
PATCH /scim/v2/Users/2819c223-7f76-453a-919d-413861904646 HTTP/1.1
Host: app.example.com
Authorization: Bearer (token issued to the provisioning service)
Content-Type: application/scim+json
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [
{ "op": "replace", "path": "active", "value": false }
]
}
SCIM moves accounts; it does not decide who should have them. That decision comes from HR data, assignment rules and approvals.
Access requests and approvals
Birthright access covers what everyone in a role needs. Everything else — a project share, an extra application, admin rights for a migration — is requested. A good request process records who asked, for what, why, who approved, and when the access ends. "Ask a colleague who already has it to add you" records none of that.
In Microsoft Entra ID Governance this is entitlement management (as of 2026). Administrators bundle groups, applications and SharePoint sites into an access package, for example "Billing data warehouse — read". A policy on the package says who may request it (employees, or users from a partner organization), who approves (the manager, then a resource owner), and how long the assignment lasts. When it expires, the access is removed automatically unless it is extended — the request process doubles as deprovisioning.
Access reviews and recertification
Automation follows HR events, but not everything is an HR event: a temporary exception that was never undone, a group nested into another group, a guest whose project ended. Access reviews — also called recertification — ask a person to confirm at a fixed interval that each access right is still needed.
In Entra, access reviews can cover group memberships, application assignments, access packages, guests and privileged roles (through PIM; see Privileged Access Management). The design choices that decide whether a review works:
- Reviewer: the manager knows the person, the resource owner knows the data; self-review is weak for anything sensitive.
- Frequency: quarterly for privileged and sensitive access, yearly for ordinary access is a common pattern.
- What happens without an answer: "remove access" is safer than "keep access", but needs good communication to avoid surprises.
- Auto-apply: denied access is removed automatically; a review whose results someone must apply by hand is a review that is often not applied.
- Recommendations: highlighting users who have not signed in for a long time helps reviewers focus.
Access creep and orphaned accounts
Access creep (also called privilege creep) is the gradual build-up of rights an identity no longer needs, mostly because movers get new access without losing the old. After three job changes, a long-serving employee may hold the access of four roles. The risk is double: a compromised account reaches far more than its owner's job, and the person can combine rights that separation of duties was meant to keep apart.
An orphaned account is an enabled account with no current owner: a leaver's account that was never disabled, a test account from a migration, a shared account whose responsible person left. Nobody notices when it is used, which makes it attractive to attackers. Ways to find them:
- Compare every account with the HR export by employee ID (the example below).
- Look for accounts without an employee ID, manager or owner attribute.
- Look for accounts that have not signed in for a long time.
# Enabled user accounts without a sign-in in the last 90 days
Search-ADAccount -AccountInactive -UsersOnly -TimeSpan 90.00:00:00 |
Where-Object Enabled |
Select-Object Name, LastLogonDate, DistinguishedName
Name LastLogonDate DistinguishedName ---- ------------- ----------------- billing.e0871 3/14/2026 8:02:11 AM CN=billing.e0871,OU=Billing,DC=ad,DC=example,DC=com test.migration 11/2/2025 4:45:37 PM CN=test.migration,OU=Staff,DC=ad,DC=example,DC=com
LastLogonDate comes from the replicated
lastLogonTimestamp attribute, which is updated only
when the stored value is more than about 9 to 14 days old, so it can
lag by up to two weeks; treat it as approximate. In Entra ID, the
signInActivity property of a user gives the last
sign-in times. Inactivity alone does not prove an account is
orphaned (someone on long leave), and activity does not prove it is
not (an attacker using it); the HR comparison is the reliable check.
A joiner-mover-leaver diff in Python
This sketch compares an HR export with directory accounts and reports leavers whose account is still enabled, movers with leftover groups, joiners without an account, and accounts that match nobody in HR:
# Compare an HR export with directory accounts: the joiner-mover-leaver diff.
TODAY = "2026-10-01"
ROLE_GROUPS = { # groups each job role should give (birthright access)
"Billing clerk": {"GG-Billing-Users"},
"Service desk analyst": {"GG-ServiceDesk", "GG-LAPS-Readers-Tier2"},
"Network engineer": {"GG-NOC", "GG-Network-Admins"},
}
HR = { # employee ID: (job role, start date, end date or None)
"E1001": ("Billing clerk", "2021-03-01", None),
"E1002": ("Network engineer", "2019-09-01", None), # was service desk
"E1003": ("Billing clerk", "2022-01-10", "2026-09-30"), # left
"E1004": ("Network engineer", "2026-10-01", None), # starts today
}
DIRECTORY = { # account: (employee ID, enabled, groups)
"billing.e1001": ("E1001", True, {"GG-Billing-Users"}),
"noc.e1002": ("E1002", True, {"GG-NOC", "GG-Network-Admins", "GG-ServiceDesk"}),
"billing.e1003": ("E1003", True, {"GG-Billing-Users"}),
"test.migration": ("E0999", True, {"GG-Billing-Users"}),
}
accounts_by_id = {emp: name for name, (emp, _, _) in DIRECTORY.items()}
for emp, (role, start, end) in sorted(HR.items()):
account = accounts_by_id.get(emp)
if end is not None and end < TODAY:
if account and DIRECTORY[account][1]:
print(f"LEAVER {emp}: left {end}, account {account} still enabled")
elif start <= TODAY and account is None:
print(f"JOINER {emp}: starts {start} as {role}, no account yet")
elif account:
extra = DIRECTORY[account][2] - ROLE_GROUPS[role]
for group in sorted(extra):
print(f"MOVER {emp}: {account} keeps {group}, not part of {role}")
for name, (emp, enabled, _) in sorted(DIRECTORY.items()):
if emp not in HR and enabled:
print(f"ORPHAN {name}: employee {emp} not in HR export")
MOVER E1002: noc.e1002 keeps GG-ServiceDesk, not part of Network engineer LEAVER E1003: left 2026-09-30, account billing.e1003 still enabled JOINER E1004: starts 2026-10-01 as Network engineer, no account yet ORPHAN test.migration: employee E0999 not in HR export
Dates in ISO format (YYYY-MM-DD) compare correctly as
strings, which keeps the example short. The set difference
DIRECTORY[account][2] - ROLE_GROUPS[role] is the heart
of mover detection: whatever the account has beyond what the current
role gives. In real life some of that is legitimately requested
access, which is why the result goes to a reviewer rather than
straight into a removal script — unless every extra right was
granted through an access package with an end date.
Guests and external identities
Contractors, auditors and partner staff also need access, but HR does not hire them. In Microsoft Entra they are usually guest users (B2B collaboration): they sign in with their own organization's account, and your tenant holds a guest object that can be added to groups and applications. Guests have their own lifecycle problem: nobody's leaver process covers them. Practical controls are a sponsor (an internal owner) for every guest, access packages with an end date for partner organizations, and access reviews that remove guests who no longer need access or have not signed in for months.
Common mistakes
- Copying a colleague's account ("make it like Sam's") for a joiner: the new person inherits years of access creep on day one.
- Movers handled as joiners: the new role is added, the old one is never taken away.
- Forgetting non-directory accounts: local application accounts, shared admin passwords the leaver knew, API tokens and personal access tokens.
- Deleting too early: an immediately deleted account can take a mailbox, files or audit context with it. Disable first, delete after a defined retention period.