---
title: "IdentityOps"
slug: identityops
category: security
term_status: emerging
evidence_status: verified
summary: "IdentityOps is the day-to-day operation of workforce and machine identities: HR-driven joiner–mover–leaver, directories and identity providers, SSO, SCIM provisioning, phishing-resistant MFA and recovery, and the lifecycle of non-human identities."
audience: [IAM engineers, IT administrators, Identity architects, Security leaders, Beginners]
last_verified: 2026-10-17
key_takeaways:
  - "IdentityOps is an emerging label with vendor-specific scopes. The practice beneath it (identity lifecycle, SSO, SCIM, MFA, IGA) is established under its own names and standards."
  - "This page owns who exists and how they prove it. What they may do belongs to IAMOps, and how access is requested, elevated and reviewed belongs to AccessOps: an editorial split, not an industry boundary."
  - "Every identity needs an authoritative source: HR for people, an owner register for machines. An account that traces to neither is an orphan to remove."
  - "Disabling an account in the identity provider does not end sessions already issued to applications. Pair deprovisioning with session and token revocation, and measure time to deprovision everywhere."
  - "Recovery is part of authentication. Help-desk password and MFA resets were a documented entry point in the Scattered Spider campaigns."
  - "Non-human identities fail quietly: Cloudflare's 2023 intrusion used a token and three service accounts it wrongly believed were unused."
related: [iamops, accessops, peopleops, cloudsecops, endpointops, complianceops, soc-ops, detectionops, secops]
---

## What IdentityOps is

### In 30 seconds

**IdentityOps** (identity operations) keeps an organisation's digital identities accurate and their authentication strong. It
creates, changes and removes accounts as people join, move and leave, driven by the HR record; connects applications through
single sign-on (SSO) and automated provisioning; enrols and recovers multi-factor authentication (MFA); and tracks machine
identities (service accounts, API keys, OAuth applications) that have no HR record at all.

**In one line: every identity that can sign in belongs to someone or something that still needs it, and proves itself with a
method that is hard to phish and hard to talk a help desk into resetting.**

**For** IAM engineers, IT administrators, identity architects and security leaders. Facts checked 2026-10-17.

::: note Term status and this page's emphasis
"IdentityOps" has no standard definition. MojoAuth defines "Identity operations (IdentityOps)" as "the day-to-day work of running
an identity system after it is deployed".[^mojo] The NHI Mgmt Group notes that "Definitions vary across vendors".[^nhimg] Opsio
uses the term for "the strategic convergence of identity and access management with security operations and IT
automation".[^opsio] "Identity Operations" is also a team name, for example in Microsoft's IAM Protect team.[^job-ms] This Atlas
splits identity across three pages: **who** exists and how they prove it (this page), **what** they may do
([IAMOps](../iamops/)), and **how and when** access is requested, elevated and reviewed ([AccessOps](../accessops/)). That split
is an **editorial distinction**; the industry draws no such line.
:::

### At a glance

| Item | Summary |
|---|---|
| Term status | **Emerging.** A label in vendor glossaries and team names, with differing scopes. The field beneath it, identity and access management (IAM), is established, with a professional body of knowledge[^idpro] and stable standards. |
| Goals | No account outlives its need; joiners productive on day one; leavers out everywhere within the SLA; phishing-resistant sign-in and recovery; every machine identity owned. |
| In scope | Joiner–mover–leaver (JML) from HR, directories and identity providers (IdPs), SSO (SAML, OIDC), SCIM provisioning, MFA enrolment and recovery, the IGA platform and birthright access, non-human identities (NHIs), identity threat signals. |
| Out of scope | Permission design, access requests and reviews, the HR process, detection: see [what it is not](#what-identityops-is-not) and [its neighbours](#how-identityops-relates-to-other-ops). |
| Prerequisites | None; basic web authentication (passwords, cookies, tokens) helps. |
| Key outputs | Lifecycle policy and SLAs, an application register, reconciliation reports, MFA and recovery standards, an NHI register, audit evidence. |

### The problem it solves

MITRE ATT&CK lists valid accounts as a route to initial access and persistence, noting that adversaries "may abuse inactive
accounts: for example, those belonging to individuals who are no longer part of an organization".[^t1078] Every incident in the
[use cases](#use-cases-and-scenarios) is an identity that should not have existed, or should not have been usable.

| Operating reality | Without IdentityOps | With IdentityOps |
|---|---|---|
| A leaver | IT learns days later; some SaaS accounts stay active | The HR termination triggers disablement, deprovisioning and session revocation within an SLA |
| A new SaaS app | Local accounts with their own passwords | SSO plus SCIM, or a documented exception |
| A lost phone | A help desk resets MFA after a friendly call | Recovery by a defined method, with notification |
| A service account | Shared password, no owner, no expiry | Registered owner, short-lived or rotated credential, end date |

### What IdentityOps is not

- **Not authorisation design.** Roles, policies and least privilege are [IAMOps](../iamops/). IdentityOps assigns *birthright*
  access (the access a role gets automatically) but does not design the permissions behind it.
- **Not privileged access.** Requests, just-in-time elevation, vaults and review campaigns are [AccessOps](../accessops/), run on
  the IGA platform this page operates.
- **Not HR.** [PeopleOps](../peopleops/) decides *that* someone joins, moves or leaves and owns the data; IdentityOps changes accounts.
- **Not a product.** An IdP or IGA suite is a tool; a stale HR feed defeats either.
- **Not customer identity first.** Customer identity (CIAM) shares the protocols but not the lifecycle; this page is workforce-first.

## Where IdentityOps comes from and where it sits

### Origins and evolution

No founding document exists. The label is recent; the standards it operates are not: SAML 2.0 (OASIS, March 2005)[^saml] and
SCIM 2.0 (2015)[^rfc7643][^rfc7644] for sign-in and provisioning; CISA's call for phishing-resistant MFA as "the gold standard"
(2022);[^cisa-mfa] NIST SP 800-63-4 (July 2025);[^sp63] the OpenID shared-signals specifications (approved as Final Specifications in September 2025);[^ssf-final] and
NIST IR 8587 on token protection (September 2026).[^ir8587]

### Glossary and mental model

| Term | Meaning on this page |
|---|---|
| **Authoritative source** | The system that decides an identity should exist: HR for employees, a sponsor record for contractors and guests, an owner register for machines. |
| **Directory / IdP** | The directory stores accounts and groups (Active Directory, LDAP); the identity provider authenticates users for applications. Microsoft Entra ID plays both roles. |
| **SSO (SAML, OIDC)** | The IdP vouches for the user with a SAML assertion or an OpenID Connect ID token, whose `sub` is "never reassigned" within an issuer.[^oidc] |
| **Provisioning (SCIM, JIT)** | Creating, updating and deactivating accounts in applications. SCIM uses an API; just-in-time (JIT) creates an account at first SSO login and has no channel for removal. |
| **Orphan / dormant account** | Orphan: no authoritative source or owner. Dormant: unused for longer than policy allows. |
| **Phishing-resistant authenticator** | One that cannot be relayed through a fake site: FIDO2/WebAuthn and PKI smart cards. Codes typed by hand (OTP, SMS) are not.[^sp63b] |
| **AAL1–AAL3** | NIST's authentication assurance levels: at least one factor; two factors; two factors with a phishing-resistant, non-exportable key.[^sp63b] |
| **Account recovery** | Regaining access after losing authenticators; NIST sets the allowed methods per AAL.[^sp63b] |
| **NHI** | A non-human identity: service account, API key, OAuth client, certificate, workload identity. |
| **IGA** | Identity governance and administration: the platform that holds lifecycle and birthright rules and produces evidence. |
| **Tier-0** | A system whose compromise gives control of everything that trusts it: the IdP, its signing keys, the directory-sync server. A term from Microsoft's administrative tier model.[^ms-connect] |
| **Shared signals** | OpenID event standards: SSF is the framework, CAEP carries session and credential changes, RISC account events such as `credential-compromise`.[^caep][^risc] |

**The mental model: three ledgers that must agree.** The *HR ledger* says who should exist. The *directory* says who can
authenticate. The *application ledger* (local accounts plus live sessions and tokens) says who is actually in. Incidents live in
the gaps: a terminated worker still enabled, a disabled user with a live session, a SaaS account the IdP never knew about.

### How IdentityOps relates to other Ops

| Page | IdentityOps focus | Their focus |
|---|---|---|
| [IAMOps](../iamops/) | Identities, groups as lifecycle containers, NHI inventory | Permission design and evaluation, policy as code |
| [AccessOps](../accessops/) | Running the IGA platform, birthright rules | Requests, elevation, PAM, break-glass, reviews |
| [PeopleOps](../peopleops/) | Account changes, reconciliation | The HR event, its data and dates |
| [CloudSecOps](../cloudsecops/) | Federation into clouds, IAM users and keys | Cloud posture and guardrails |
| [EndpointOps](../endpointops/) | Device signals in sign-in | Managing devices |
| [DetectionOps](../detectionops/), [SOC Ops](../soc-ops/) | Identity signals and containment actions | Detections, triage |
| [ComplianceOps](../complianceops/) | Producing lifecycle evidence | Mapping it to frameworks |

::: figure The identity trio and its neighbours
```mermaid
flowchart TB
  PEO["PeopleOps: HR events"] -->|"join, move, leave"| IDO
  IDO["IdentityOps: who exists, how they prove it"]
  IDO -->|"identities, groups"| IAM["IAMOps:<br/>what they may do"]
  IDO -->|"runs the IGA platform"| ACC["AccessOps:<br/>request, elevate, review"]
  IDO -->|"sign-in signals"| SIG["DetectionOps, SOC Ops:<br/>identity alerts"]
  IDO -.->|"lifecycle evidence"| CMP["ComplianceOps:<br/>evidence"]
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  class IDO core
  class PEO,IAM,ACC,SIG,CMP neutral
```
Text equivalent: PeopleOps sends join, move and leave events into IdentityOps, which owns who exists and how they prove it.
IdentityOps hands identities and groups to IAMOps (what identities may do), runs the IGA platform that AccessOps uses for
requests, elevation and reviews, and sends sign-in signals to DetectionOps and SOC Ops (identity alerts). ComplianceOps draws
lifecycle evidence from IdentityOps (dotted edge).
:::

The split can be mapped onto NIST CSF 2.0's PR.AA subcategories, an **editorial mapping** (the CSF assigns no teams): PR.AA-01
to -04 (identities, credentials, proofing, authentication, assertions) to IdentityOps; PR.AA-05 (permissions, least privilege and
their review) to IAMOps for design and AccessOps for reviews.[^csf]

## Principles and operating model

### Charter and principles

This charter is **this Atlas's proposal**; no official IdentityOps manifesto exists.

**Mission:** every identity that can authenticate is known, owned, needed and strongly verified, and the evidence shows it.

1. **Every identity has an authoritative source.** NIST SP 800-53 asks organisations to "Align account management processes with
   personnel termination and transfer processes".[^ac2]
2. **Events, not tickets, change accounts.** A termination entered once in HR drives every downstream change.
3. **Deprovisioning ends when sessions end.**
4. **Phishing-resistant by default; recovery no weaker than sign-in.** A security key reset by phone is as strong as the phone call.
5. **Machines get a lifecycle too:** owner, purpose, expiry, rotation, decommissioning.
6. **The IdP is tier-0 production:** configuration, federation trusts and signing keys under change control.
7. **Measure the gaps between the three ledgers**, not tickets closed.

**Non-goals:** designing permissions ([IAMOps](../iamops/)) and approving elevated access ([AccessOps](../accessops/)).

### Fundamentals: the building blocks

| Block | Job | Failure if missing |
|---|---|---|
| Authoritative sources | Decide who should exist | Orphans; missed leavers |
| Directory | Store accounts, groups, attributes | Duplicates; no stable identifier |
| Identity provider | Authenticate; issue assertions and tokens | Passwords per app |
| Lifecycle engine / IGA | Turn HR events into account changes | Manual tickets; drift |
| Provisioning connectors | Create, update, deactivate in apps (SCIM 2.0, vendor APIs) | Accounts survive leavers |
| Authenticators and recovery | Prove control; restore it safely | Phishing; help-desk takeover |
| Signals | Detect misuse; share session changes | Late detection |

Examples of each are in the [tooling landscape](#tooling-landscape).

### Roles, ownership and RACI

R = responsible, A = accountable, C = consulted, I = informed.

| Activity | HR / PeopleOps | Identity team | Application owner | Service desk | SOC |
|---|---|---|---|---|---|
| Record join, move, leave with effective date | A/R | I | — | — | — |
| Lifecycle rules and birthright policy | C | A/R | C | I | — |
| Connect an app (SSO, SCIM) | — | R | A | I | — |
| Leaver deprovisioning within SLA | C | A/R | R (non-SCIM apps) | I | I |
| MFA enrolment and recovery standard | — | A/R | — | R (executes) | C |
| NHI registration and rotation | — | A | R (each owner) | — | I |
| Identity alert containment | — | R | I | — | A |
| Lifecycle evidence for audit | C | A/R | C | — | — |

Managers and sponsors confirm contractor and guest need.

### Lifecycle: joiner, mover, leaver

::: figure Identity lifecycle states for a worker
```mermaid
stateDiagram-v2
  direction TB
  [*] --> Prehire: HR record created
  Prehire --> Active: start date, after proofing
  Prehire --> Deleted: offer withdrawn or no-show
  Active --> Active: mover recalculates birthright
  Active --> Suspended: leave of absence
  Suspended --> Active: return
  Active --> Disabled: leaver effective date
  Suspended --> Disabled: leaver effective date
  Disabled --> Active: rehire under policy
  Disabled --> Deleted: retention period ends
  Deleted --> [*]
```
Text equivalent: an identity starts in Prehire when HR creates the record and becomes Active on the start date, once the person
is proofed; if the offer is withdrawn or the person never starts, it is deleted, not left as an orphan. A move keeps it Active
but recalculates birthright access. Leave moves it to Suspended and back. On the leaver effective date it becomes Disabled.
A rehire can reactivate it under policy; otherwise it is Deleted when the retention period ends.
:::

- **Prehire.** Create the identity disabled so day-one access is ready. Microsoft's HR provisioning matches records by employee
  ID by default and asks how rehires and termination dates are handled.[^ms-hr]
- **Proof before bootstrap.** A bootstrap credential is only as good as the check on who receives it. HR or the hiring manager
  matches the person to identity documents before day one. For remote hires, Microsoft, writing on North Korean IT workers using
  stolen or "rented" identities, advises several on-camera sessions, asking people to "physically hold driver’s licenses,
  passports, or identity documents up to camera", and notarised proof of identity.[^jasper] Hand the pass only to the proofed
  person, in person or on live video. NIST SP 800-63A defines identity assurance levels (IAL1–IAL3) if you need a formal
  standard.[^sp63a]
- **Joiner.** Enable on the start date, assign birthright groups, and enrol phishing-resistant MFA with a bootstrap credential such
  as Entra's Temporary Access Pass, "a time-limited passcode".[^ms-tap]
- **Mover.** Recalculate birthright and *remove* what the old role granted; movers are where access accumulates (Lab 1 checks it).
- **Leaver.** Disable, deprovision downstream, revoke sessions and tokens, delete after retention (the
  [leaver workflow](#end-to-end-workflow-a-leaver) gives the product behaviour).
- **Rehire.** Reactivate with *no* previous access, or create a new identity, by written policy.

### Operating model and governance

**Cadence:** HR events, provisioning and alert routing run continuously; reconciliation and provisioning errors daily, with an
owner per exception; a sample of assisted recoveries and new-app intake weekly; metrics and the NHI expiry report monthly; the
leaver drill and an IdP configuration and federation-trust review quarterly.

SLAs are yours to set: NIST AC-2 leaves notification and disabling periods to the organisation.[^ac2] Review IdP changes (new
trust, admin or authentication policy) like production changes; attackers have added rogue federation trusts
([Case 4](#use-cases-and-scenarios)).

## Architecture and workflow

### Reference architecture

::: figure Reference architecture: from HR record to application session
```mermaid
flowchart TB
  HR["HR system: authoritative for people"] --> LC
  REG["NHI owner register"] --> LC
  LC["Lifecycle engine / IGA: rules, birthright"] --> DIR
  DIR["Directory and IdP"] -->|"SSO, SCIM,<br/>CAEP events"| APPS
  APPS["SaaS and internal apps"]
  DIR -->|"sign-in logs,<br/>risk"| SOC["SOC and detection"]
  APPS --> REC["Reconciliation<br/>HR and NHI register vs<br/>directory export vs app exports"]
  DIR --> REC
  HR --> REC
  REG --> REC
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  class LC,DIR,REC core
  class HR,REG,APPS,SOC neutral
```
Text equivalent: the HR system and the NHI owner register feed a lifecycle engine or IGA platform, which writes to the directory
and IdP. The IdP signs users in to applications with SAML or OIDC, provisions them with SCIM, sends session events with CAEP, and
sends sign-in logs and risk to the SOC. Reconciliation compares all three ledgers: HR and the NHI register (who should exist), the
directory's account export (who can authenticate) and application account exports (who is actually in).
:::

| Component | Trust boundary notes |
|---|---|
| HR system | A write to HR can create an account: protect HR admin roles |
| Lifecycle engine / IGA | Connector credentials are high-value NHIs |
| Directory and IdP | Tier-0: admin roles, trusts and signing keys; see NIST IR 8587 on key management and token verification[^ir8587] |
| Provisioning connectors | One scoped, rotated token per app |
| Reconciliation | Reads each ledger directly, not the provisioning log |

**Alternatives.** *Cloud-first:* lifecycle in the IdP's own features (licence-dependent[^ms-lcw]). *Hybrid:* HR provisions into
Active Directory, which synchronises to the cloud;[^ms-hr] the sync server "must be treated as a Tier 0 component",[^ms-connect]
and Windows services should prefer group managed service accounts, whose passwords Windows manages.[^ms-gmsa] *IGA suite:*
for many sources and complex rules, at the cost of another tier-0 system.

### End-to-end workflow: a leaver

::: figure Leaver sequence from HR termination to evidence
```mermaid
sequenceDiagram
  autonumber
  participant HR as HR
  participant LC as Lifecycle
  participant IDP as IdP
  participant APP as SCIM app
  participant EV as Evidence
  HR->>LC: termination 17:00
  LC->>IDP: disable account
  LC->>IDP: revoke sessions
  IDP->>APP: active=false
  Note over IDP,APP: Entra: next cycle
  APP-->>IDP: 200 or 204
  Note over LC,EV: 6. timestamps recorded as evidence
```
Text equivalent: HR sends a termination with an effective time to the lifecycle engine, which disables the IdP account and
revokes sessions and refresh tokens. The IdP sends each SCIM app a PATCH setting active to false (Okta on deactivation, Entra in
its next provisioning cycle); the app replies 200 or 204. Finally the engine records each system's timestamp as evidence
(shown as a note across the lifelines).
:::

**Happy path, and what it depends on.** Both IdPs deprovision SCIM apps by setting `active` to false, but only if configured
to: Okta needs "Deactivate Users" enabled for the app;[^okta-scim] Entra needs the update action and the `active`
mapping.[^ms-prov] Okta sends the request when the user is deactivated; Entra's cycles run "approximately every 40 minutes" (as
of October 2026).[^ms-scim]

**Urgent leavers in Entra.** On-demand provisioning can disable a user unassigned from an app, not one already disabled in
Entra; a push "typically takes less than 30 seconds".[^ms-pod] In this page's reading, that means: unassign the person from
each critical app, push it, then disable the account. This reverses the disable-first order above and in Microsoft's emergency
steps[^ms-revoke-access] and leaves the account enabled until the last push, so use it only when removal inside the apps
matters more than those minutes. It needs the app scoped to "Sync only assigned users and groups",[^ms-prov] and apps that
authorise with OAuth (Microsoft names G Suite, Box and Slack) need their provisioning job stopped first.[^ms-pod] An unassigned
user leaves the job's management, so a later soft-deletion sends no delete.[^ms-prov] Otherwise disable first and deactivate
critical apps directly (the Tier B path).

**When deactivation means deletion.** Okta never sends a delete,[^okta-scim] but Entra sends a DELETE to apps without
soft-delete support and, unless the Delete target action is cleared, when the source user is permanently deleted
(automatically 30 days after soft deletion, or earlier by hand).[^ms-prov] RFC 7643 leaves the meaning of `active` to each
application.[^rfc7643]

**Exception paths** (each needs an owner and an SLA):

- **Sessions outlive the account.** Entra access tokens last about 60–90 minutes by default (Microsoft's pages say "1 hour"
  and "60-90 minutes"), and up to 28 hours where CAE applies (as of October 2026);[^ms-revoke-access][^ms-tokens] "the user
  loses access when the access token expires".[^ms-revoke-access] `revokeSignInSessions` invalidates refresh tokens and session cookies after "a small delay of a few
  minutes".[^ms-revoke] Okta's session revocation "doesn't clear the sessions created for web or native apps".[^okta-api] Apps
  with their own sessions need their own revocation or short lifetimes.
- **Provisioning is broken.** An Entra job whose calls keep failing enters quarantine, where its cycles slow to once a day, and is disabled
  after more than four weeks in quarantine;[^ms-prov] leavers silently stay active in that app.
- **Legal hold.** Disable sign-in now; delay deletion; check each app's deprovisioning behaviour first. In Entra, clear the Delete
  target action; for apps without soft delete, where even a disable becomes a DELETE, preserve the data before disabling or handle
  that app by hand (Tier B or C).[^ms-prov] In Okta, deactivating the user is "a **destructive** operation" that deprovisions all
  assigned apps; suspension keeps assignments.[^okta-api]

## Practices, frameworks and tools

### Core practices and playbooks

| Practice | Goal | Trigger | Owner | Verification |
|---|---|---|---|---|
| HR-driven provisioning | Accounts follow HR | HR event | Identity team | Daily reconciliation |
| App connection standard | SSO and SCIM everywhere | New app | App owner | Register coverage |
| Mover recalculation | No access accumulation | Attribute change | Lifecycle engine | Group difference report |
| Leaver drill | Prove deprovisioning | Quarterly | Identity team | Per-app timestamps within SLA |
| Dormant sweep | Disable unused accounts, guests included; excluded: HR-suspended identities, emergency accounts,[^ms-emerg] NHIs with a registered longer cadence (owner confirms first) | Daily | Identity team | Window of 90–180 days, as Microsoft suggests;[^ms-inactive] exclusion list reviewed quarterly |
| Phishing-resistant rollout | Remove phishable sign-in | Programme | Identity team | Coverage and usage metrics |
| Verified recovery | No reset on a caller's word | Every reset | Service desk | Weekly sample review |
| NHI registration and rotation | Machine identities owned and current | New credential; monthly | Each owner | Register vs inventory |
| IdP change control | Catch rogue admins and trusts | IdP change | Identity team | Unapproved-change alert |

### Authentication and recovery practice

**Phishing resistance.** NIST SP 800-63B-4 requires verifiers to "offer at least one phishing-resistant authentication option at
AAL2" and requires phishing resistance for US federal staff.[^sp63b] CISA calls
FIDO/WebAuthn "The only widely available phishing-resistant authentication"; PKI smart cards also qualify.[^cisa-mfa] Roll out in
risk order: IdP and cloud administrators, then email, remote access and finance, then everyone. Synced passkeys suit most staff,
but their keys are exportable, so NIST excludes them from AAL3:[^sp63b] give administrators device-bound security keys. Where push
MFA remains, limit prompts[^sp63b] and use number matching as an interim step.[^cisa-mfa]

**Passwords, while they last:** at least 15 characters (8 when used only with a second factor), no composition rules, no
periodic changes, a forced change on evidence of compromise, and a blocklist of common and breached passwords.[^sp63b]

**Recovery.** NIST recognises saved recovery codes, issued recovery codes, recovery contacts and repeated identity proofing; any
other method, such as a help-desk agent, must be "based on a risk analysis and documented". At AAL2, recovery needs two codes
obtained by different methods, a code plus a still-bound authenticator, or repeated proofing, and every recovery or new
authenticator triggers a notification.[^sp63b] CISA's advisory shows the attack: Scattered Spider posed as employees "to convince
IT and/or helpdesk staff to provide sensitive information, reset the employee's password, and transfer the employee's MFA to a
device they control".[^cisa-ss] A defensible design:

1. **Self-service first**, through a second registered authenticator (NIST encourages "at least two separate means of
   authentication"[^sp63b]).
2. **Assisted recovery** issues a one-time bootstrap credential[^ms-tap] only after the manager (or sponsor) confirms the person
   on a live video call or in person, and delivers it through that manager or to an already-registered device, never read out to
   the caller. A call-back to the HR phone number is weak evidence on its own: Scattered Spider conducted "SIM swaps" before
   calling help desks,[^cisa-ss] and NIST asks verifiers to consider "SIM change, number porting" before using the phone
   network to deliver an out-of-band secret[^sp63b] (a rule written for authentication; this page applies it by analogy to
   call-back verification).
3. **Privileged and high-risk accounts** recover only through repeated proofing (in person or supervised video against an
   identity document) **and** a second administrator's approval.
4. **Watch the seam:** alert when a new MFA method follows a reset within hours, and when a federation trust or IdP admin is added.

### Non-human identity lifecycle practice

Machine identities have no HR record, so nothing signals their end. OWASP's Non-Human Identities Top 10 (2025) starts with
"Improper Offboarding", "the inadequate deactivation or removal of non-human identities (NHIs) such as service accounts and access
keys when they are no longer needed", and includes long-lived secrets, NHI reuse and human use of NHIs.[^owasp-nhi]

1. **Inventory per system:** IdP app registrations and service principals, cloud IAM users and keys, CI/CD secrets, SaaS API
   tokens. AWS's credential report covers IAM users' passwords, keys and MFA, but only the first two access keys and no
   service-specific credentials.[^aws-credrep] It lists users only: IAM roles need their own inventory and last-used
   data,[^aws-rlu] designed with [IAMOps](../iamops/).
2. **Register** owner, purpose, environment, expiry and expected cadence: a quarter-end job is not dormant at 90 days, so the
   [dormant sweep](#core-practices-and-playbooks) and Lab 3's idle rule must read the registered cadence. No owner, no credential.
3. **Prefer short-lived credentials:** AWS advises requiring "workloads to use temporary credentials with IAM roles";[^aws-bp]
   designing that federation belongs to [IAMOps](../iamops/).
4. **Rotate what must stay long-lived**, and record it.
5. **Decommission with evidence:** check last use, disable, wait a business cycle, delete ([Case 2](#use-cases-and-scenarios)
   shows the cost of guessing).

### Methods, standards and frameworks

| Source | Kind | Use in IdentityOps |
|---|---|---|
| NIST SP 800-63-4[^sp63] | US federal guideline | Assurance levels; authentication and recovery design |
| NIST SP 800-53 AC-2[^ac2] | Control catalogue | Account management SLAs and evidence |
| NIST CSF 2.0 PR.AA[^csf] | Voluntary framework | Mapping controls across the identity trio |
| CISA ZTMM v2.0[^ztmm] | Maturity model (US federal) | Calibrating the maturity model |
| SAML 2.0, OIDC Core, RFC 9700[^saml][^oidc][^rfc9700] | Protocol standards | Federation and OAuth configuration baselines |
| SCIM 2.0[^rfc7644] | Protocol standard | Provisioning requirement for apps |
| SSF, CAEP, RISC[^ssf-final] | Protocol standards (final 2025) | Session and risk events across vendors |
| OWASP NHI Top 10[^owasp-nhi] | Community risk list | NHI review checklist |
| MITRE ATT&CK[^t1078] | Adversary knowledge base | Detection coverage with the SOC |

RFC 9700 doubles as an IdP configuration test: the implicit grant "SHOULD NOT" be used and the resource owner password
credentials grant "MUST NOT be used".[^rfc9700] Most of these documents are US-originated; check your jurisdiction before
treating any as a legal requirement.

### Tooling landscape

Categories first; named products are examples verified in documentation (as of October 2026), not endorsements.

| Category | Verified examples | Selection criteria |
|---|---|---|
| HR source | Workday, SAP SuccessFactors[^ms-hr] | Effective dates, worker types, API |
| Directory and IdP | Microsoft Entra ID, Okta, Active Directory | SCIM, phishing-resistant methods, revocation API, logs |
| Lifecycle and IGA | Entra lifecycle workflows;[^ms-lcw] dedicated IGA suites | Connectors, testable rules, audit reports |
| Authenticators | FIDO2 security keys, passkeys, PIV cards | Exportability, recovery, device support |
| Identity threat signals | Entra ID Protection;[^ms-risk] tools sold as identity threat detection and response (ITDR)[^ms-itdr] | Coverage beyond one IdP; judge ITDR by what it adds to the IdP's own detections |
| NHI inventory | AWS IAM credential report;[^aws-credrep] IdP app registrations | Last-used data; credential types covered |

**Lock-in.** Every application trusts the IdP's signing keys, which makes it the hardest system to replace; standard
connections (SAML or OIDC plus SCIM) keep an exit possible.

### Automation and AI

Automate the routine lifecycle; keep human approval for contractors without HR records, rehires, privileged identities and bulk
changes (a mass termination can be an HR error). Machine learning already sits inside IdP risk detections. A language model may
summarise a sign-in investigation; it must never be the verifier in a recovery flow. AI agents are NHIs
([going further](#advanced-topics-and-emerging-direction)).

## IdentityOps in practice

### Reference implementation

An illustrative design for a 2,000-person company with a cloud HR system, one cloud IdP and about 150 SaaS applications. The
policy below is the single place where lifecycle rules live; the schema is **this Atlas's illustration**, not a product format.

_Example status: YAML syntax-checked by the Atlas validator; not loaded into any product._

```yaml
# identity-lifecycle-policy.yaml
version: 3
sources:
  employees: {system: hr, match_on: employee_id}
  contractors: {system: sponsor-register, max_days: 180, sponsor_required: true}
  machines: {system: nhi-register, owner_required: true, max_credential_days: 90}
birthright:
  all: [all-staff, email]
  department:
    finance: [finance, erp]
    sales: [sales, crm]
    engineering: [engineering, git]
slas:
  joiner_ready_hours_before_start: 24
  leaver_disable_idp_hours: 4
  leaver_deprovision_all_apps_hours: 24
  dormant_disable_days: 90
  dormant_exclusions: [hr_suspended, emergency_access, nhi_with_registered_cadence]
leaver_actions:
  - disable_idp_account
  - revoke_sessions_and_refresh_tokens
  - scim_deactivate_connected_apps
  - open_tickets_for_non_scim_apps
  - remove_authenticators
retention:
  delete_after_days: 30
  unless: legal_hold
rehire:
  restore_previous_access: false
recovery:
  self_service: {any_of: [second_authenticator]}
  standard:
    require_all: [manager_confirms_on_live_video_or_in_person, one_time_pass]
    pass_delivery: [via_manager, to_registered_device]   # never read out on the call
  privileged:
    require_all: [in_person_or_supervised_video_proofing, second_admin_approval]
  notify_user_on: [recovery, new_authenticator]
```

Every application gets a connection tier in a register, chosen with the
[decision framework](#decision-framework-connecting-an-application):

| Tier | Connection | Leaver handling | Review |
|---|---|---|---|
| A | SSO (SAML or OIDC) plus SCIM | Automatic `active=false` | Monthly provisioning error report |
| B | SSO plus a vendor API script | Scripted, logged | Monthly script run evidence |
| C | SSO with JIT only | Daily account export reconciled; manual removal within SLA | Weekly |
| D | No SSO (local passwords) | Ticket to the app owner | Exception expires; quarterly attestation |

### Hands-on labs

Labs 1 and 3 run anywhere with Python 3 and were executed for this page. Lab 2 needs a test tenant and was **not executed**.

#### Lab 1 (beginner): reconcile the three ledgers

**Objective:** find leavers, orphans, duplicate accounts, movers with leftover access, missing joiners, dormant accounts and
weak MFA from an HR roster and an IdP export. **Prerequisites:** Python 3.8 or later; no account, cost or risk; 30 minutes.

1. Save the data as `jml.json` and the script as `jml_reconcile.py`.
2. Run `python3 jml_reconcile.py jml.json`, then `echo $?`.
3. Fix the findings in `jml.json`: set `enabled` to `false` for cara, dev (on long leave, confirmed by the manager) and
   test-admin; remove `finance` and `erp` from ben's groups and set his `mfa` to `phishing-resistant`; add an account for E104
   (`eli@example.com`, groups `all-staff`, `email`, `finance`, `erp`, `phishing-resistant`, last sign-in today). Run again.
4. What-if: in the original file, change `now` to `2026-10-16T09:00:00Z` and run again. Then restore it and add a second
   enabled account for E100 (copy ana's record with `"upn": "ana2@example.com"`) and run again.

_Example status: verified by execution (Python 3.8.10, 2026-10-16; re-run 2026-10-17 after the duplicate check was added); output
below is verbatim._

```json
{
  "policy": {
    "now": "2026-10-16T12:00:00Z",
    "leaver_sla_hours": 4,
    "dormant_days": 90,
    "birthright": {
      "all": ["all-staff", "email"],
      "finance": ["finance", "erp"],
      "sales": ["sales", "crm"],
      "engineering": ["engineering", "git"]
    }
  },
  "hr": [
    {"id": "E100", "status": "active", "dept": "finance"},
    {"id": "E101", "status": "active", "dept": "sales"},
    {"id": "E102", "status": "terminated", "dept": "sales",
     "end": "2026-10-16T06:00:00Z"},
    {"id": "E103", "status": "active", "dept": "engineering"},
    {"id": "E104", "status": "active", "dept": "finance"}
  ],
  "idp": [
    {"upn": "ana@example.com", "emp": "E100", "enabled": true,
     "groups": ["all-staff", "email", "finance", "erp"],
     "mfa": "phishing-resistant", "last": "2026-10-15T09:00:00Z"},
    {"upn": "ben@example.com", "emp": "E101", "enabled": true,
     "groups": ["all-staff", "email", "sales", "crm", "finance", "erp"],
     "mfa": "otp", "last": "2026-10-16T08:00:00Z"},
    {"upn": "cara@example.com", "emp": "E102", "enabled": true,
     "groups": ["all-staff", "email", "sales", "crm"],
     "mfa": "phishing-resistant", "last": "2026-10-16T07:30:00Z"},
    {"upn": "dev@example.com", "emp": "E103", "enabled": true,
     "groups": ["all-staff", "email", "engineering", "git"],
     "mfa": "none", "last": "2026-06-01T10:00:00Z"},
    {"upn": "test-admin@example.com", "emp": null, "enabled": true,
     "groups": ["all-staff", "email"], "mfa": "none",
     "last": "2026-09-30T23:10:00Z"}
  ]
}
```

```python
"""Reconcile an HR roster with identity-provider accounts (IdentityOps Lab 1)."""
import json
import sys
from datetime import datetime


def ts(value):
    return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")


def main(path):
    with open(path) as f:
        data = json.load(f)
    pol = data["policy"]
    now = ts(pol["now"])
    hr = {w["id"]: w for w in data["hr"]}
    accounts = {}  # employee id -> every account that claims it
    for a in data["idp"]:
        if a["emp"]:
            accounts.setdefault(a["emp"], []).append(a)
    findings = []

    for a in data["idp"]:
        if a["emp"] not in hr and a["enabled"]:
            findings.append("ORPHAN   %s has no HR record" % a["upn"])
    for emp, w in sorted(hr.items()):
        owned = accounts.get(emp, [])
        if w["status"] == "active" and not owned:
            findings.append("JOINER   %s active in HR, no account" % emp)
            continue
        enabled = [a for a in owned if a["enabled"]]
        if len(enabled) > 1:
            findings.append("DUPLICATE %s has %d enabled accounts (%s)"
                            % (emp, len(enabled), ", ".join(a["upn"] for a in enabled)))
        for a in owned:
            if w["status"] == "terminated" and a["enabled"]:
                hours = (now - ts(w["end"])).total_seconds() / 3600
                late = "BREACH" if hours > pol["leaver_sla_hours"] else "within SLA"
                findings.append("LEAVER   %s enabled %.1f h after end date (%s)"
                                % (a["upn"], hours, late))
            if w["status"] == "active":
                allowed = set(pol["birthright"]["all"] + pol["birthright"][w["dept"]])
                extra = sorted(set(a["groups"]) - allowed)
                if extra:
                    findings.append("MOVER    %s (%s) keeps %s"
                                    % (a["upn"], w["dept"], ", ".join(extra)))

    humans = [a for a in data["idp"] if a["emp"] in hr and a["enabled"]
              and hr[a["emp"]]["status"] == "active"]
    strong = [a for a in humans if a["mfa"] == "phishing-resistant"]
    for a in data["idp"]:
        idle = (now - ts(a["last"])).days
        if a["enabled"] and idle > pol["dormant_days"]:
            findings.append("DORMANT  %s last sign-in %d days ago" % (a["upn"], idle))
        if a["enabled"] and a["mfa"] != "phishing-resistant":
            findings.append("MFA      %s uses %s" % (a["upn"], a["mfa"]))

    for f in findings:
        print(f)
    print("Phishing-resistant coverage (active staff): %d/%d (%.1f%%)"
          % (len(strong), len(humans), 100.0 * len(strong) / len(humans)))
    print("Findings: %d" % len(findings))
    return 1 if findings else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1]))
```

**Expected output (step 2), exit code 1:**

```text
ORPHAN   test-admin@example.com has no HR record
MOVER    ben@example.com (sales) keeps erp, finance
LEAVER   cara@example.com enabled 6.0 h after end date (BREACH)
JOINER   E104 active in HR, no account
MFA      ben@example.com uses otp
DORMANT  dev@example.com last sign-in 137 days ago
MFA      dev@example.com uses none
MFA      test-admin@example.com uses none
Phishing-resistant coverage (active staff): 1/3 (33.3%)
Findings: 8
```

**Validation.** Step 3 prints `Phishing-resistant coverage (active staff): 3/3 (100.0%)` and `Findings: 0`, exit code 0.
Step 4 first prints `LEAVER   cara@example.com enabled 3.0 h after end date (within SLA)`: the same gap passes or fails on time
alone, so leaver SLAs need timestamps, not daily flags. The duplicate run adds `DUPLICATE E100 has 2 enabled accounts
(ana@example.com, ana2@example.com)` (Findings: 9): two live accounts for one person, the rehire failure in the
[troubleshooting table](#common-failures-and-troubleshooting). **Troubleshooting:** `KeyError: 'dept'` means an HR record lacks a department;
a `strptime` `ValueError` means a timestamp without the trailing `Z`. **Teardown:** delete the files. **Next:** run it on real
exports, handled under your data rules.

#### Lab 2 (intermediate): deactivate a SCIM account and revoke sessions

**Objective:** run a leaver by hand against a **test** application and IdP tenant, as automation would. **Prerequisites:** a
SCIM 2.0 test endpoint and token; for Okta an OAuth token with the `okta.users.manage` scope (Okta recommends scoped OAuth tokens
over API tokens[^okta-api]); for Entra a token with `User.RevokeSessions.All`;[^ms-revoke] curl 7.76 or later for
`--fail-with-body`.[^curl] **Risk:** never run it against production users.

_Example status: JSON and bash syntax-checked; not executed (needs a SCIM test app and an IdP test tenant). Request shapes from
RFC 7644, Okta and Microsoft Graph documentation._[^rfc7644][^okta-scim][^okta-api][^ms-revoke]

`deactivate.json`, the body Okta sends to deactivate a user:[^okta-scim]

```json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{"op": "replace", "value": {"active": false}}]
}
```

```bash
#!/usr/bin/env bash
# Leaver drill against TEST systems only.
set -euo pipefail
SCIM="https://scim.example.com/scim/v2"          # placeholder SCIM base URL
: "${SCIM_TOKEN:?set a test SCIM bearer token}"
: "${OKTA_USER_ID:?set the Okta user id}" "${OKTA_ACCESS_TOKEN:?set an Okta OAuth token}"
: "${ENTRA_USER_ID:?set the Entra object id or UPN}" "${GRAPH_TOKEN:?set a Graph token}"
LEAVER="test.leaver@example.com"                 # SCIM userName in the test app
CURL=(curl -sS --fail-with-body)                 # stop on HTTP 4xx/5xx

# 1. Find the user (RFC 7644 filter; Okta sends the same query)
"${CURL[@]}" -G "$SCIM/Users" -H "Authorization: Bearer $SCIM_TOKEN" \
  -H "Accept: application/scim+json" \
  --data-urlencode "filter=userName eq \"$LEAVER\"" > lookup.json
PICK='import json; print(json.load(open("lookup.json"))["Resources"][0]["id"])'
ID=$(python3 -c "$PICK")

# 2. Deactivate (soft delete): PATCH active=false
"${CURL[@]}" -X PATCH "$SCIM/Users/$ID" -H "Authorization: Bearer $SCIM_TOKEN" \
  -H "Content-Type: application/scim+json" --data @deactivate.json

# 3a. Okta: end IdP sessions and revoke OAuth tokens (oauthTokens defaults to false).
#     Also clears remembered-device MFA state: forgetDevices defaults to true on
#     Identity Engine and false on Classic Engine; pass it explicitly to be sure.
OKTA="https://example.okta.com"                  # placeholder org URL
"${CURL[@]}" -X DELETE \
  "$OKTA/api/v1/users/$OKTA_USER_ID/sessions?oauthTokens=true&forgetDevices=true" \
  -H "Authorization: Bearer $OKTA_ACCESS_TOKEN"

# 3b. Entra: invalidate refresh tokens and session cookies
GRAPH="https://graph.microsoft.com/v1.0"
"${CURL[@]}" -X POST "$GRAPH/users/$ENTRA_USER_ID/revokeSignInSessions" \
  -H "Authorization: Bearer $GRAPH_TOKEN" -H "Content-Type: application/json" -d ''
```

**Expected output:** step 1 returns a `ListResponse` with one resource; step 2 HTTP 200 with the user showing `"active": false`,
or 204 with no body;[^rfc7644] step 3a HTTP 204 with no body, and the user's remembered devices are forgotten as a side
effect;[^okta-api] step 3b a 2xx response.[^ms-revoke] Any 4xx or 5xx
stops the script and prints the error body. **Validation:** the user cannot sign in to the test app and an old refresh
token fails; issued access tokens stay valid until they expire.[^ms-revoke-access] **Troubleshooting:** an empty `Resources`
array means the app matches on another attribute; HTTP 403 means a missing scope or role. **Teardown:** reactivate with
`"active": true` or delete the test user.

#### Lab 3 (advanced): audit machine identities from a credential report

**Objective:** find unowned, unused, idle and unrotated access keys and human IAM users, using the real credential-report
columns.[^aws-credrep] **Prerequisites:** Python 3.8 or later; 45 minutes; step 5 needs IAM read access to a non-production
account.

1. Save `make_report.py`, `nhi_audit.py` and the owner register `owners.json`.
2. Run `python3 make_report.py > credential_report.csv` to create the synthetic report.
3. Run `python3 nhi_audit.py credential_report.csv owners.json`.
4. Remediate in the data: delete the `report-export`, `jane.admin` and `old-etl` rows from `USERS` (after their owners confirm),
   and change `legacy-backup` to a new key created and used on `2026-10-16`. Run steps 2 and 3 again.
5. Optional, real account (not executed here): `aws iam generate-credential-report`, then
   `aws iam get-credential-report --query Content --output text | base64 --decode > credential_report.csv`. The `Content` field
   is Base64-encoded;[^aws-cli-gcr] reports can be generated at most once every four hours.[^aws-credrep] A real report also
   has a row for the account root user, which the script flags NO-OWNER: check it separately for MFA and no access
   keys.[^aws-root]

_Example status: steps 1–4 verified by execution (Python 3.8.10, 2026-10-16, re-run 2026-10-17), output verbatim; step 5 not
executed._

```python
"""Write a synthetic AWS IAM credential report (real column names, fake users)."""
import csv
import sys

COLUMNS = [
    "user", "arn", "user_creation_time", "password_enabled", "password_last_used",
    "password_last_changed", "password_next_rotation", "mfa_active",
    "access_key_1_active", "access_key_1_last_rotated", "access_key_1_last_used_date",
    "access_key_1_last_used_region", "access_key_1_last_used_service",
    "access_key_2_active", "access_key_2_last_rotated", "access_key_2_last_used_date",
    "access_key_2_last_used_region", "access_key_2_last_used_service",
    "cert_1_active", "cert_1_last_rotated", "cert_2_active", "cert_2_last_rotated",
]
T = "T00:00:00+00:00"
USERS = [  # user, password, mfa, key 1 created, key 1 last used
    ("ci-deploy", "false", "false", "2026-09-01", "2026-10-15"),
    ("legacy-backup", "false", "false", "2024-03-10", "2026-10-14"),
    ("report-export", "false", "false", "2025-11-20", None),
    ("jane.admin", "true", "false", "2026-08-01", "2026-10-12"),
    ("old-etl", "false", "false", "2025-01-05", "2025-06-30"),
]

out = csv.DictWriter(sys.stdout, fieldnames=COLUMNS, lineterminator="\n")
out.writeheader()
for user, pwd, mfa, created, used in USERS:
    row = {c: "N/A" for c in COLUMNS}
    row.update({
        "user": user, "arn": "arn:aws:iam::111122223333:user/" + user,
        "user_creation_time": created + T, "password_enabled": pwd,
        "mfa_active": mfa, "access_key_1_active": "true",
        "access_key_1_last_rotated": created + T,
        "access_key_1_last_used_date": used + T if used else "N/A",
        "access_key_2_active": "false", "cert_1_active": "false",
        "cert_2_active": "false",
    })
    out.writerow(row)
```

```json
{
  "as_of": "2026-10-16",
  "max_key_age_days": 90,
  "max_idle_days": 90,
  "owners": {
    "ci-deploy": "platform-team",
    "legacy-backup": "storage-team",
    "old-etl": "data-team"
  }
}
```

```python
"""Audit IAM users and access keys from an AWS credential report (IdentityOps Lab 3)."""
import csv
import json
import sys
from datetime import datetime, timezone

EMPTY = ("N/A", "no_information", "not_supported", "")


def days_since(value, now):
    if value in EMPTY:
        return None
    return (now - datetime.fromisoformat(value)).days


def main(report_path, register_path):
    with open(register_path) as f:
        reg = json.load(f)
    now = datetime.strptime(reg["as_of"], "%Y-%m-%d").replace(tzinfo=timezone.utc)
    with open(report_path, newline="") as f:
        rows = list(csv.DictReader(f))
    findings, owned, ok, keys = [], 0, 0, 0
    for r in rows:
        user = r["user"]
        if user in reg["owners"]:
            owned += 1
        else:
            findings.append("NO-OWNER  %s: no owner registered" % user)
        if r["password_enabled"].lower() == "true":
            mfa = "on" if r["mfa_active"].lower() == "true" else "OFF"
            findings.append("HUMAN     %s: console password, MFA %s; federate"
                            % (user, mfa))
        for k in ("1", "2"):
            if r["access_key_%s_active" % k].lower() != "true":
                continue
            keys += 1
            age = days_since(r["access_key_%s_last_rotated" % k], now)
            idle = days_since(r["access_key_%s_last_used_date" % k], now)
            key = "%s key %s" % (user, k)
            if idle is None:
                findings.append("UNUSED    %s: never used, %d days old" % (key, age))
            elif idle > reg["max_idle_days"]:
                findings.append("IDLE      %s: last used %d days ago" % (key, idle))
            elif age > reg["max_key_age_days"]:
                findings.append("ROTATE    %s: %d days old, in use" % (key, age))
            else:
                ok += 1
    for line in findings:
        print(line)
    print("Owner coverage: %d/%d users" % (owned, len(rows)))
    print("Active keys within policy: %d/%d" % (ok, keys))
    print("Findings: %d" % len(findings))
    return 1 if findings else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1], sys.argv[2]))
```

**Expected output (step 3), exit code 1:**

```text
ROTATE    legacy-backup key 1: 950 days old, in use
NO-OWNER  report-export: no owner registered
UNUSED    report-export key 1: never used, 330 days old
NO-OWNER  jane.admin: no owner registered
HUMAN     jane.admin: console password, MFA OFF; federate
IDLE      old-etl key 1: last used 473 days ago
Owner coverage: 3/5 users
Active keys within policy: 2/5
Findings: 6
```

**Validation.** Step 4 prints `Owner coverage: 2/2 users`, `Active keys within policy: 2/2` and `Findings: 0`. **Lesson:**
`jane.admin` is a person on an IAM user; AWS wants humans to "use federation with an identity provider",[^aws-bp] so the fix is
federation, not rotation. **Troubleshooting:** a `fromisoformat` `ValueError` means a `Z` suffix (Python 3.8 needs `+00:00`).
**Teardown:** delete the files; treat a real report as sensitive data.

### Use cases and scenarios

Four sourced incidents, each a different gap. Facts are the organisations' or responders' own accounts; lessons are this page's.

#### Case 1: a legacy test tenant and an OAuth app (sourced)

From late November 2023, Midnight Blizzard "used a password spray attack to compromise a legacy non-production test tenant
account",[^msrc-mb] which "did not have multifactor authentication (MFA) enabled". The actor then used "a legacy test OAuth
application that had elevated access to the Microsoft corporate environment" and created more OAuth applications to reach
mailboxes.[^ms-mb] **Gap:** a test account outside MFA policy and a legacy test app that still had elevated access.
**Practice:** inventory every tenant and app registration; MFA for every interactive account. **Success measure:** zero
interactive accounts without MFA; zero privileged app registrations without an owner.

#### Case 2: a service account, session tokens and credentials nobody rotated (sourced)

Okta found that from 28 September to 17 October 2023 an attacker used "a service account stored in the system itself", whose
credentials "had been saved into the employee's personal Google account"; session tokens in support files let the attacker hijack
five customers' sessions.[^okta-rca] Cloudflare, one of them, reported that "we failed to rotate one service token and three
service accounts (out of thousands)" because "mistakenly it was believed they were unused".[^cf] **Gap:** machine-identity use
assumed, not measured. **Practice:** prove rotation item by item against the inventory, with last-used evidence; treat session
tokens as credentials. **Success measure:** rotated ÷ exposed = 100%, checked by a second person.

#### Case 3: SaaS accounts signing in with passwords only (sourced)

Mandiant's 2024 investigation of UNC5537, which targeted Snowflake customer instances, found accounts "not configured with
multi-factor authentication", infostealer-harvested credentials that "had not been rotated for as long as four years", and no
network allow lists; about 165 potentially exposed organisations were notified.[^mandiant] **Gap** (this page's reading): app
accounts protected by a password alone, outside any IdP's MFA policy. **Practice:** tier every SaaS app
([reference implementation](#reference-implementation)); enforce SSO and MFA; reconcile local accounts. **Success measure:**
critical apps at Tier A or B; local accounts without MFA = 0.

#### Case 4: the help desk as the bypass (sourced)

CISA's Scattered Spider advisory (revised 29 July 2025) describes help-desk password and MFA resets, push bombing, SIM swapping,
self-registered MFA tokens and, historically, a federated IdP added to the victim's SSO tenant.[^cisa-ss] In May 2025 the UK
NCSC asked organisations to "review helpdesk password reset processes".[^ncsc] **Gap:** recovery weaker than authentication.
**Practice:** [tiered recovery](#authentication-and-recovery-practice) and alerts on post-reset MFA and federation changes.
**Success measure:** every sampled reset followed the script.

## Measure, govern, pay

### Metrics and KPIs

MojoAuth suggests "time to provision and deprovision, orphaned accounts found per review, and the rate of account-recovery
requests";[^mojo] the set below makes them precise.

| KPI | Definition / formula | Pitfall | Review |
|---|---|---|---|
| Leaver compliance (end to end) | Leavers disabled in the IdP **and** deactivated in every connected app within SLA ÷ leavers | Measuring only the IdP | Monthly |
| Time to disable and deprovision (p50, p95) | IdP disable time, and last app deactivation time, − HR effective time | Late HR entry looks like an identity delay (track HR lag apart); provisioning cycles add lag per app | Monthly |
| Orphan rate | Enabled accounts with no authoritative source or owner ÷ enabled accounts | Disabled orphans counted as solved | Weekly |
| Phishing-resistant coverage | Active people with a phishing-resistant authenticator ÷ active people | Counting "MFA enabled", SMS included | Monthly |
| Phishing-resistant usage | Holders' interactive sign-ins using such a method ÷ holders' interactive sign-ins | Mixing in non-holders hides weak fallbacks | Monthly |
| Automated deprovisioning coverage | User–app assignments on Tier A or B apps ÷ all user–app assignments (sum over apps of their user counts) | A per-app count hides one large local-account app | Quarterly |
| Joiner day-one readiness | Joiners working with birthright access at start ÷ joiners | Counting account creation only | Monthly |
| Verified recovery rate | Sampled assisted resets that followed the script ÷ sampled resets | Sampling easy cases | Weekly |
| NHI credential compliance | Machine credentials owned and within age and idle limits ÷ machine credentials | Inventory gaps flatter the ratio | Monthly |

**Worked calculation (illustrative scenario).** HR records 40 leavers in a month. The IdP disabled 36 within the 4-hour SLA; HR
entered the other four two days late. Three of the 36 still had a Tier C account after 24 hours.

- IdP-only compliance = 36 ÷ 40 = **90.0%**; end to end = (36 − 3) ÷ 40 = **82.5%**. The gap has two owners: HR entry lag
  ([PeopleOps](../peopleops/)) and Tier C reconciliation (IdentityOps).
- Of 1,400 staff, 1,190 have a FIDO2 key or passkey: coverage = **85.0%**. Of those 1,190 holders' 62,000 interactive sign-ins,
  41,000 used one: usage = **66.1%**. A third of holders' sign-ins still use weaker methods, so policy still accepts them from
  people who own a strong one: require a phishing-resistant method by policy.

**Targets:** set from the baseline, then tighten. Coverage and Tier A share lead; leaver compliance and identity incidents lag.

### Security, privacy and compliance

| Risk | Signal | Mitigation | Owner |
|---|---|---|---|
| Password spray, credential stuffing | Detections such as "Password spray" and "Leaked credentials"[^ms-risk] | Phishing-resistant MFA; blocklist; no local passwords | Identity, SOC |
| MFA prompt bombing (T1621)[^t1621] | Many push prompts for one user | Number matching, prompt limits, phishing-resistant methods | Identity |
| Help-desk social engineering | New MFA method soon after an assisted reset | Tiered recovery; notification; alert | Service desk, SOC |
| Token theft and replay | Detections such as "Anomalous token"[^ms-risk] | Short sessions; revocation; signing-key protection[^ir8587] | Identity |
| Rogue federation trust (T1484.002)[^t1484] or authentication-policy change (T1556)[^t1556] | IdP change outside change control | Few admins; change alerts; trust review | Identity |
| Orphaned machine identity | Credential with no owner or recent use | Register, expiry, decommissioning | Owners |

**Privacy.** Identity records are personal data. Send applications only the attributes they need, restrict HR-sourced
attributes, and set retention for disabled accounts and sign-in logs with [PrivacyOps](../privacyops/). Rules vary by
jurisdiction; this page gives no legal advice.

**Compliance.** Auditors usually ask for leavers with a timestamp per system, reconciliation results and MFA coverage, against
controls such as NIST AC-2.[^ac2] Mapping evidence to ISO/IEC 27001, SOC 2 and other frameworks belongs to
[ComplianceOps](../complianceops/).

### Cost, capacity and sustainability

- **Licence tiers drive design.** Entra HR-driven provisioning needs P1 or P2 plus the HR application's licence;[^ms-hr]
  lifecycle workflows need Entra ID Governance or Entra Suite;[^ms-lcw] even the last-sign-in property for dormant checks needs
  P1 or P2,[^ms-inactive] and the inactive-guest report needs ID Governance or Entra Suite.[^ms-guest] SaaS vendors often put SCIM in higher plans (Docker's guide is marked Business).[^docker-scim] Price the
  Tier A target, not just the IdP.
- **Labour.** Tier C and D apps and assisted recoveries are recurring manual work. Measure both.
- **Hardware.** Two security keys per administrator (one spare), plus replacement.
- **Resilience.** Every sign-in depends on the IdP: see the [FAQ](#faq) on outages.
- **Sustainability** is not material here beyond hardware lifecycle.

### Assurance of the operating model

- **Independent reconciliation** reads application data, not the provisioning log.
- **Control tests:** the quarterly leaver drill (a test identity in Tier A–D apps); sign in with a disabled test account; replay an
  old refresh token; an authorised social-engineering test of the help-desk script.
- **Decay signals:** provisioning errors or quarantined jobs, growth of Tier C and D, a spike in recovery requests, expired
  exceptions still open, and NHIs without owners.

## When things go wrong

### Common failures and troubleshooting

| Symptom | Likely cause | Diagnosis | Remediation | Prevention |
|---|---|---|---|---|
| Leaver still active in an app | Tier C or D app; provisioning job failing or quarantined[^ms-prov] | Job status; app export | Deactivate by hand; fix connector | Alert on quarantine; daily reconciliation |
| Disabled user still reads mail or files | Sessions and tokens issued before disablement | Logs after the disable time | Revoke sessions and refresh tokens | Revocation as a leaver step; continuous access evaluation where supported[^ms-cae] |
| Duplicate identity after rehire | New employee ID; matching rule misses | Compare HR and directory IDs (Lab 1 prints `DUPLICATE`) | Merge or disable the duplicate | Written rehire policy[^ms-hr] |
| Mover keeps old access | Rules only add groups | Group difference against birthright | Remove extra groups | Recalculate on every change (Lab 1) |
| Hundreds of accounts disabled at once | HR feed error or changed scoping filter | Spike in disable events | Pause the job; restore from the audit log | Bulk-change threshold needing approval |
| An app's SSO fails one morning | Expired SAML signing certificate | App error; certificate dates | Update certificate and metadata | Certificate expiry calendar |

### Worked runbook: a fraudulent MFA reset

**Illustrative scenario.** At 10:05 the SOC sees a new authenticator registered for a finance manager, 20 minutes after a
phoned-in MFA reset, then a sign-in from an unfamiliar device: the pattern CISA describes for Scattered Spider.[^cisa-ss]

::: figure Response to a suspected fraudulent MFA reset
```mermaid
flowchart TB
  A["Alert: new MFA method after a reset"] --> B{"User confirms via known channel?"}
  B -->|"yes, it was them"| Z["Close; record and review the script"]
  B -->|"no or unreachable"| C["Contain: block sign-in, revoke sessions, remove new methods"]
  C --> D["Scope: sign-ins, consents, inbox rules, roles, federation"]
  D --> E["Eradicate: remove rogue apps, rules, trusts; rotate exposed secrets"]
  E --> F["Recover: re-proof the user, issue new authenticators"]
  F --> G["Learn: fix recovery script and detections"]
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  class A,C risk
  class B decision
  class D,E,F,G,Z core
```
Text equivalent: an alert of a new MFA method after a reset leads to one question: does the user confirm it through a known
channel? If yes, close and review the script. If not, contain, scope, eradicate, recover and learn, in that order.
:::

1. **Verify (0–10 min).** Reach the user through the manager or a device registered before the reset, not the number on the
   ticket; a call to the HR number alone may ring a SIM-swapped phone.
2. **Contain (10–20 min).** Block sign-in; revoke sessions and refresh tokens (Graph `revokeSignInSessions`[^ms-revoke] or Okta
   `DELETE /api/v1/users/{userId}/sessions?oauthTokens=true&forgetDevices=true`, which also clears remembered devices, a
   default on Identity Engine but not on Classic Engine[^okta-api]); remove the attacker's authenticator; end app sessions
   that outlive the IdP's ([exception paths](#end-to-end-workflow-a-leaver)).
3. **Scope (20–90 min).** Sign-ins since the reset; new OAuth consents and app registrations (Midnight Blizzard kept access through
   OAuth apps[^ms-mb]); inbox rules[^ms-risk]; role assignments; federation and authentication-policy changes.
4. **Eradicate.** Remove rogue consents, apps, rules and trusts; ask owners to rotate secrets she could reach.
5. **Recover.** Re-proof her through the privileged path, issue new authenticators and confirm the recovery notifications NIST
   requires.[^sp63b]
6. **Learn (within 5 days).** Compare the ticket with the script, fix the failed step, and send the alert to
   [DetectionOps](../detectionops/). Incident coordination follows [IncidentOps](../incidentops/); adversary-facing work
   (evidence, forensics, notification) belongs to [ResponseOps](../responseops/).

**Done when** no session issued after the reset survives, no rogue method or consent remains, and the script fix is live.

### Anti-patterns and myths

- **"Disabled in the directory means offboarded."** Sessions, local app accounts and machine credentials survive. Measure end to end.
- **"MFA is 100%."** If SMS and push count, phishable sign-ins hide in the number. Report phishing-resistant coverage *and* usage.
- **Forced password expiry.** NIST says verifiers "SHALL NOT require subscribers to change passwords periodically";[^sp63b]
  CISA's advisory agrees.[^cisa-ss] Change on evidence of compromise.
- **JIT as the provisioning strategy.** It never removes accounts; Docker, for example, recommends "SCIM with JIT turned
  off".[^docker-scim]
- **The help desk as the default recovery path.** It makes social engineering a supported feature.
- **People sharing service accounts.** OWASP lists "Human Use of NHI" as a top risk;[^owasp-nhi] it also destroys attribution.
- **Myth: SSO concentrates risk, so avoid it.** It concentrates control too: one place to enforce MFA, revoke and log. Treat the
  IdP as tier-0 instead of scattering passwords.

## Deciding and adopting

### When to use IdentityOps and when not to

**Name and staff it** when you have dozens of SaaS apps, contractors or guests without HR records, a hybrid directory, auditors
asking for leaver evidence, machine identities growing faster than people, or a recent identity incident.

**Do not build a separate practice** in a small organisation (a few dozen people, a handful of apps): one administrator with the
IdP's built-in features, SSO wherever possible, security keys for administrators and a monthly reconciliation are enough. **Do
not buy an IGA suite** before HR data is clean and the application register exists: rules on bad attributes automate bad
decisions.

**Hidden responsibilities:** an IdP on-call with change control; a service desk with a script and authority to refuse callers;
app owners who accept SLAs for what cannot be automated.

### Decision framework: connecting an application

::: figure How to connect a new application
```mermaid
flowchart TB
  A{"SAML or OIDC?"} -->|"no"| D["Replace, block or Tier D exception"]
  A -->|"yes"| C{"SCIM on our plan?"}
  C -->|"yes"| TA["Tier A: SSO + SCIM"]
  C -->|"no"| E{"Deactivation API?"}
  E -->|"yes"| TB["Tier B: SSO + script"]
  E -->|"no"| TC["Tier C: daily reconciliation"]
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class A,C,E decision
  class TA,TB,TC core
  class D risk
```
Text equivalent: without SAML or OIDC, replace or block the app, or record a Tier D exception with an expiry. With SSO and SCIM
on your plan, use Tier A. Without SCIM but with a user-deactivation API, use Tier B. Otherwise use Tier C with daily
reconciliation; if the app holds sensitive data or many users, negotiate SCIM with the vendor meanwhile.
:::

**IdP-native lifecycle or an IGA suite?** Ask four questions: several authoritative sources? complex birthright rules? many
non-SCIM or on-premises targets? heavy audit reporting? Two or more "yes" answers usually justify a dedicated platform and its
running cost.

### Maturity model

This model is **this Atlas's proposal**, calibrated against CISA's Zero Trust Maturity Model, whose identity pillar runs from
Traditional (the agency "manually orchestrates (onboards, offboards, and disables)" identities) to Optimal ("continuously
validates identity with phishing-resistant MFA").[^ztmm] Thresholds are suggestions.

| Level | Observable behaviour | Exit criteria to the next level |
|---|---|---|
| 0 Ad hoc | Accounts by email and ticket; local passwords; no reconciliation | Sources named; application register exists |
| 1 Centralised | One IdP; SSO for major apps; any MFA; JML by ticket | HR feed live; leaver SLA measured |
| 2 Automated | HR-driven JML; SCIM for top apps; daily reconciliation; NHI inventory | Leaver compliance ≥ 90%; orphan rate tracked |
| 3 Governed | Phishing-resistant admins and most staff; tiered recovery; owned NHIs; IdP change control; drills | Phishing-resistant usage ≥ 80%; all NHIs owned |
| 4 Continuous | Session events shared (CAEP) where supported; risk-driven re-authentication; short-lived machine credentials | Sustained ≥ 95% leaver compliance |

Levels 0–1 resemble CISA's Traditional stage, 2 Initial, 3 Advanced and 4 Optimal; the mapping is approximate.

### Adoption roadmap: 30/60/90 days

| Window | Actions | Milestone and metric |
|---|---|---|
| Days 1–30 | Name owners; build the tiered application register; run Lab 1 on real exports; security keys for administrators; recovery script | Baseline KPIs |
| Days 31–60 | HR-driven provisioning for one population; SCIM for the ten largest apps; daily reconciliation; NHI inventory (Lab 3) | First automated leavers; owned exception list |
| Days 61–90 | Session revocation for leavers; first drill; alerts on post-reset MFA and federation changes; Tier D expiry dates | Drill evidence; leaver compliance rising |

**Dependencies:** clean HR dates ([PeopleOps](../peopleops/)), app owners' time, licences, SOC capacity. **Quick wins:** delete
orphans; security keys for IdP administrators; remove SMS where a stronger method exists.

## Toolkit

### Checklists

**Quick start**

- [ ] Authoritative source named for employees, contractors, guests and machines
- [ ] Application register with a tier and owner per app
- [ ] Leaver SLA measured from the HR effective time
- [ ] Security keys for every IdP and cloud administrator
- [ ] Recovery script approved by security and the service desk
- [ ] New hires proofed against identity documents (live video for remote hires) before a bootstrap pass is issued

**Application onboarding preflight**

- [ ] SSO tested; local password sign-in disabled where possible
- [ ] Deactivation enabled (Okta "Deactivate Users"; Entra update action and `active` mapping) and tested with a test user:
  what `active=false` means in this app, whether it deletes, and how long the push takes
- [ ] Only needed attributes mapped; session lifetime and revocation documented
- [ ] Provisioning token registered as an NHI with owner and expiry

**Quarterly review**

- [ ] Leaver drill across Tier A–D apps, timestamps recorded
- [ ] No provisioning job in error or quarantine
- [ ] Admin roles, federation trusts and signing-certificate expiry dates reviewed
- [ ] Alerts firing for new admins, new trusts and authentication-policy changes
- [ ] Legal-hold path tested (disable without delete)

### Templates

**1. Application connection record**

```text
APPLICATION CONNECTION RECORD                          v1  (owner signs)
Application / vendor:            ______________   Business owner: ________
Data classification:             public | internal | confidential | restricted
Users (now / in 12 months):      ____ / ____
SSO protocol:                    SAML | OIDC | none (=> Tier D exception)
Provisioning:                    SCIM | API script | JIT only | manual
Tier:                            A | B | C | D        Exception expiry: ______
Meaning of deactivate here:      blocks sign-in | frees licence | deletes data
App session lifetime / revoke:   ______ / how: ____________________________
Attributes sent:                 ____________________ (minimum needed only)
Provisioning credential:         NHI id ______  owner ______  expiry ______
Leaver SLA accepted by owner:    yes / no      Next review: ______________
```

**2. Assisted recovery script (service desk)**

```text
ASSISTED MFA / PASSWORD RECOVERY                                    v2
1. Never act on the number, email or chat the caller gives you.
2. Look up the person in HR. A call-back to the HR number is NOT proof
   on its own: numbers can be SIM-swapped or ported.
3. The manager (or sponsor) confirms the person on a live video call or
   in person, reached through a known channel.
4. Privileged or finance role?  STOP. Route to in-person or supervised
   video proofing AND approval by a second administrator.
5. Issue a ONE-TIME bootstrap pass valid <= 1 hour. Deliver it via the
   manager or to a device registered before the request; never read
   it out on the call. Never set a password or register an
   authenticator on the user's behalf.
6. Record: ticket, verification steps, approver, delivery channel, time.
7. Tell the caller the user will be notified of the reset.
8. Pressure, urgency or "the CEO needs this now" = escalate to security.
```

**3. Non-human identity registration**

_Example status: YAML syntax-checked by the Atlas validator; field names are this Atlas's suggestion._

```yaml
nhi_id: nhi-0427
kind: oauth-client              # service-account | api-key | oauth-client | certificate
system: idp-prod
purpose: "Nightly export of invoices to the data warehouse"
owner: team-finance-data        # a team, plus a named individual below
owner_person: a.rossi@example.com
environment: production         # never shared with test
cadence: nightly                # expected use; the dormant sweep compares against it
credential:
  type: client-secret
  max_age_days: 90
  last_rotated: 2026-09-30
permissions_ref: iamops-review-2026-q3   # design and review live in IAMOps
expires: 2027-03-31             # re-register or decommission by this date
decommission:
  disable_first: true
  observe_days: 14
```

### Deep comparisons

**Provisioning methods**

| Method | Creates | Updates | Removes | Main risk |
|---|---|---|---|---|
| HR-driven into the directory | Yes | Yes | Yes | Dirty HR data spreads everywhere |
| SCIM 2.0 to apps | Yes | Yes | Deactivates (`active=false`) | Connector token theft; quarantined jobs |
| Vendor API script | Yes | Partly | If written | Script rot when the API changes |
| JIT at first SSO login | Yes | At sign-in, if supported | **No** | Accounts outlive leavers; reconciliation needed |
| Manual ticket | Yes | Slowly | Slowly | Delay and forgotten steps |

### FAQ

**Is IdentityOps just IAM with a new name?** Largely, for the operational half of IAM; the label stresses running the lifecycle as
a measured production service.

**Do we need an IGA product?** Not to start; ask the four questions in the
[decision framework](#decision-framework-connecting-an-application).

**Disable or delete a leaver?** Disable at once, delete later, and first learn what each app does on "disable": some connectors
delete ([legal hold](#end-to-end-workflow-a-leaver)). Keep the account disabled for the retention period, longer under legal hold.

**Contractors have no HR record. What is their source?** A sponsor register: a named employee sponsors each contractor, with an
end date that disables the account unless renewed. Guest (B2B) accounts need the same rule; Microsoft's inactive-guest report
(an Entra ID Governance or Entra Suite feature) defaults to 90 days without sign-in.[^ms-guest]

**What if the IdP is down?** Existing sessions last until they expire; new sign-ins fail. Microsoft advises "two or more
emergency access accounts", cloud-only and not federated;[^ms-emerg] practise with them. The procedure belongs to
[AccessOps](../accessops/).

## Going further

### Advanced topics and emerging direction

- **Shared signals (final 2025, early adoption).** CAEP events such as `session-revoked` and `credential-change` let an IdP tell
  applications to cut a session it has revoked:[^caep] the standard answer to sessions outliving accounts. Check each app's
  support first.
- **Token and signing-key protection (new guidance).** NIST IR 8587 (September 2026) covers protecting tokens and assertions
  "from forgery, theft, and misuse", including key management, token verification and life-cycle controls.[^ir8587]
- **Passkeys at workforce scale (maturing).** Recovery and device turnover are the open operational questions.
- **AI agents as identities (unsettled).** No standard defines their lifecycle yet; treat each as an NHI with an owner, purpose,
  expiry and short-lived credential.

### Learning path

| Stage | Do this |
|---|---|
| Prerequisites | HTTP, cookies and TLS basics; what a token is |
| 1 day | Read the authenticator, phishing-resistance and recovery sections of SP 800-63B-4;[^sp63b] run Lab 1 |
| 1 week | In a test tenant, connect one SSO app and one SCIM app; run Lab 2 |
| 30 days | Run Lab 3 on a non-production cloud account; build the application register; run one leaver drill |
| Specialisation | Federation and key security, IGA design, shared signals, identity proofing |
| Portfolio | A reconciliation tool; an NHI register; a recovery script with a tabletop record |

The IDPro Body of Knowledge is a vendor-neutral reading list for the wider IAM field.[^idpro]

## Wrap-up

### Executive recap and cheat sheet

| Topic | Remember |
|---|---|
| Definition | Running who exists and how they prove it |
| Decisions | Authoritative source per identity type; app tiers; recovery tiers; IdP-native or IGA suite |
| Practices | HR-driven JML; SCIM plus session revocation; daily reconciliation; NHI register; IdP change control |
| Metrics | End-to-end leaver compliance; time to disable; orphan rate; phishing-resistant coverage and usage; verified recovery rate |
| Watch-outs | Sessions outlive accounts; JIT never removes; help-desk resets; machine credentials believed unused |
| Next action | Run Lab 1 on real exports this week; enrol IdP administrators on security keys |

### A judgement call: who may reset MFA?

::: decision A CFO wants fewer lockouts; security wants no phone resets
**Situation (illustrative).** A 3,000-person company with an outsourced service desk handles about 300 assisted MFA resets a
month, and executives complain about waiting.

**Option A: faster phone resets.** The agent verifies the caller with employee ID, manager's name and date of birth, then resets.
**Option B: tiered recovery** as [designed above](#authentication-and-recovery-practice): self-service first; otherwise a
one-time pass delivered through the manager after a live video check; proofing **and** a second administrator for privileged
and finance roles.

**Assumptions:** most resets follow a phone change; the IdP supports a time-limited bootstrap pass;[^ms-tap] managers can be
reached on video within the working day.

**Reasoning.** Option A relies on facts an attacker can collect: CISA notes the group's calls are "enriched by access to
personal information derived from social media" and database leaks.[^cisa-ss] NIST allows agent-assisted recovery only on a
documented risk analysis, with notification of every recovery.[^sp63b] Option B costs minutes for some users and days for a few
executives; registering two authenticators removes most of that cost and cuts reset volume.

**Recommendation:** Option B, with a 60-day drive to register a second authenticator, measured by recovery volume, time to
recover and the verified-recovery rate. **Uncertainty:** video checks may themselves be spoofed as tools improve; review the
privileged path yearly and after any incident.
:::

### Learning objectives and mastery check

After this page you can **explain** the three-ledger model and the identity trio; **design** a leaver flow that ends sessions;
**choose** connection and recovery tiers; **evaluate** an estate with the KPIs and the maturity model.

1. HR marks a leaver at 09:00; the IdP account is disabled at 09:20; at 11:00 the leaver is still editing a document in a SaaS app
   connected by SCIM. Give two likely reasons.

::: answer Show answer for question 1
Not the access token: one issued before 09:20 had expired by 10:50 at the latest (Entra's default is 60–90 minutes;[^ms-tokens]
services that support CAE cut their longer tokens on disable[^ms-cae]). Likely: the app's own session survives, and `active=false`, whose meaning each
app defines,[^rfc7643] may only block new sign-ins; or the deactivation never arrived (a failing or quarantined job,[^ms-prov] or
Okta's "Deactivate Users" left off[^okta-scim]). Entra's 40-minute cycle explains a short delay, not 100 minutes.[^ms-scim]
Revoke app sessions as a leaver step and measure end to end.
:::

2. Your dashboard shows "MFA enabled: 100%". Why is that not evidence of strong authentication?

::: answer Show answer for question 2
It counts phishable methods; NIST does not treat typed codes (OTP, SMS) as phishing-resistant.[^sp63b] Report phishing-resistant
coverage, and usage among holders: below 100% means weak fallbacks are still accepted from people who own a strong method.
:::

3. After a supplier breach your team rotated "all" exposed credentials, yet one was used weeks later. What was missing?

::: answer Show answer for question 3
An inventory with last-used evidence to prove completeness against; Cloudflare's missed credentials were "believed they were
unused".[^cf] Measure rotated ÷ exposed, item by item.
:::

4. A SaaS app holding customer contracts offers SAML with JIT, no SCIM on your plan and no user API. Which tier, and what interim
   control?

::: answer Show answer for question 4
Tier C: JIT never removes accounts, so reconcile the app's user export daily and remove leavers within the SLA. Because the data
is sensitive, negotiate SCIM with a deadline ([decision framework](#decision-framework-connecting-an-application)).
:::

5. Design challenge: name three alerts that catch help-desk recovery abuse.

::: answer Show answer for question 5
A new authenticator registered within hours of an assisted reset; a new federated IdP or trust (ATT&CK T1484.002; Scattered
Spider historically added one[^cisa-ss]) or an authentication-policy change; and a risky sign-in or token anomaly for the reset
user.[^ms-risk]
:::

### Conclusion

IdentityOps is a new name for old, unglamorous work, and the work matters more than the name. Its core is simple to state and
hard to sustain: three ledgers that agree every day; a sign-in method a fake website cannot steal; a recovery path a confident
caller cannot talk through; machine identities that end when their purpose ends. None of the incidents on this page defeated
cryptography: a test account had no MFA, a service account was believed unused, a password was four years old, a help desk was
persuaded. If you can show, with timestamps, that leavers are out everywhere, that sign-in resists phishing and that every machine
identity has an owner, you are doing IdentityOps, whatever your teams are called.

## Sources and further reading

Claim-by-claim evidence: `research/identityops-ledger.md`. Product documentation changes often: the identity team should re-check
the product facts on this page (cycle intervals, token defaults, licence tiers, API versions) at its quarterly IdP configuration
review, and before relying on any of them.

[^mojo]: [Identity Operations: What It Means](https://mojoauth.com/glossary/identity-operations) — MojoAuth glossary, updated 2026-09-28. Checked 2026-10-16.
[^nhimg]: [What Is Identity Operations?](https://nhimg.org/glossary/identity-operations/) — NHI Mgmt Group glossary, updated 2026-06-07. Checked 2026-10-16.
[^opsio]: [What is IdentityOps?](https://opsiocloud.com/in/knowledge-base/what-is-identityops/) — Opsio, 2025-08-25 (updated 2025-10-13), as seen 2026-10-16; [archived copy](https://web.archive.org/web/20261017074119/https://opsiocloud.com/in/knowledge-base/what-is-identityops/) of 2026-10-17. Checked 2026-10-16.
[^job-ms]: [Security Operations Engineer (Identity Operations team)](https://jobs.anitab.org/companies/microsoft/jobs/66613821-security-operations-engineer) — Microsoft job posting via AnitaB.org, undated ("Posted 6+ months ago"), as seen 2026-10-16; [archived copy](https://web.archive.org/web/20261017074242/https://jobs.anitab.org/companies/microsoft/jobs/66613821-security-operations-engineer) of 2026-10-17. Checked 2026-10-16.
[^idpro]: [IDPro Body of Knowledge](https://idpro.org/body-of-knowledge/) — IDPro. Checked 2026-10-16.
[^sp63]: [SP 800-63-4, Digital Identity Guidelines](https://csrc.nist.gov/pubs/sp/800/63/4/final) — NIST, 2025-07-31. Checked 2026-10-16.
[^sp63a]: [SP 800-63A-4, Identity Proofing and Enrollment](https://pages.nist.gov/800-63-4/sp800-63a.html) — NIST, 2025. Checked 2026-10-17.
[^sp63b]: [SP 800-63B-4, Authentication and Authenticator Management](https://pages.nist.gov/800-63-4/sp800-63b.html) — NIST, 2025. Checked 2026-10-16, re-checked 2026-10-17.
[^csf]: [The NIST Cybersecurity Framework (CSF) 2.0](https://nvlpubs.nist.gov/nistpubs/CSWP/NIST.CSWP.29.pdf) — NIST CSWP 29, 2024-02-26. Checked 2026-10-16.
[^ac2]: [SP 800-53 Rev. 5 (Release 5.2.0), control AC-2](https://csrc.nist.gov/pubs/sp/800/53/r5/upd1/final) — NIST, release 5.2.0 of 2025-08-27; control text read in the [OSCAL catalog](https://github.com/usnistgov/oscal-content). Checked 2026-10-16.
[^ztmm]: [Zero Trust Maturity Model, Version 2.0](https://www.cisa.gov/sites/default/files/2023-04/zero_trust_maturity_model_v2_508.pdf) — CISA, April 2023. Checked 2026-10-16.
[^cisa-mfa]: [Implementing Phishing-Resistant MFA](https://www.cisa.gov/sites/default/files/publications/fact-sheet-implementing-phishing-resistant-mfa-508c.pdf) — CISA fact sheet, October 2022. Checked 2026-10-16.
[^saml]: [SAML V2.0 Technical Overview](https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) — OASIS, 2008-03-25 (cites the March 2005 core specification). Checked 2026-10-16.
[^oidc]: [OpenID Connect Core 1.0 incorporating errata set 2](https://openid.net/specs/openid-connect-core-1_0.html) — OpenID Foundation, 2023-12-15. Checked 2026-10-16.
[^rfc9700]: [RFC 9700, Best Current Practice for OAuth 2.0 Security](https://www.rfc-editor.org/rfc/rfc9700.html) — IETF, January 2025. Checked 2026-10-16.
[^rfc7643]: [RFC 7643, SCIM: Core Schema](https://www.rfc-editor.org/rfc/rfc7643.html) — IETF, September 2015. Checked 2026-10-16.
[^rfc7644]: [RFC 7644, SCIM: Protocol](https://www.rfc-editor.org/rfc/rfc7644.html) — IETF, September 2015. Checked 2026-10-16.
[^ssf-final]: [Three Shared Signals Final Specifications Approved](https://openid.net/three-shared-signals-final-specifications-approved/) — OpenID Foundation, 2025-09-02. Checked 2026-10-16.
[^caep]: [OpenID Continuous Access Evaluation Profile 1.0](https://openid.net/specs/openid-caep-1_0.html) — OpenID Foundation, 2025-08-29. Checked 2026-10-16.
[^risc]: [OpenID RISC Profile Specification 1.0](https://openid.net/specs/openid-risc-1_0-final.html) — OpenID Foundation, 2025-08-29. Checked 2026-10-17.
[^ir8587]: [NIST IR 8587, Protecting Tokens and Assertions from Forgery, Theft, and Misuse](https://csrc.nist.gov/pubs/ir/8587/final) — NIST Internal Report (authors from NIST, Accenture Federal Services and CISA), 2026-09-15. Checked 2026-10-16, authorship re-checked 2026-10-17.
[^owasp-nhi]: [OWASP Top 10 Non-Human Identities Risks – 2025](https://owasp.org/www-project-non-human-identities-top-10/2025/top-10-2025/) — OWASP, 2025. Checked 2026-10-16.
[^t1078]: [Valid Accounts, T1078](https://attack.mitre.org/techniques/T1078/) — MITRE ATT&CK, modified 2026-05-12. Checked 2026-10-16.
[^t1621]: [Multi-Factor Authentication Request Generation, T1621](https://attack.mitre.org/techniques/T1621/) — MITRE ATT&CK, modified 2025-10-24. Checked 2026-10-16.
[^t1484]: [Domain or Tenant Policy Modification: Trust Modification, T1484.002](https://attack.mitre.org/techniques/T1484/002/) — MITRE ATT&CK, modified 2026-05-12. Checked 2026-10-17.
[^t1556]: [Modify Authentication Process, T1556](https://attack.mitre.org/techniques/T1556/) — MITRE ATT&CK, modified 2026-05-12. Checked 2026-10-16.
[^ms-lcw]: [What are lifecycle workflows?](https://learn.microsoft.com/en-us/entra/id-governance/what-are-lifecycle-workflows) — Microsoft Learn, 2026-03-12. Checked 2026-10-16.
[^ms-hr]: [Plan cloud HR application to Microsoft Entra user provisioning](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/plan-cloud-hr-provision) — Microsoft Learn, 2025-07-24. Checked 2026-10-16.
[^ms-prov]: [How Application Provisioning works in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/how-provisioning-works) — Microsoft Learn, 2025-03-04 (updated 2026-02-05). Checked 2026-10-16, re-checked 2026-10-17.
[^ms-pod]: [Provision a user or group on demand](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/provision-on-demand) — Microsoft Learn, 2025-03-04 (updated 2026-10-14). Checked 2026-10-17.
[^ms-scim]: [Tutorial: Develop and plan provisioning for a SCIM endpoint](https://learn.microsoft.com/en-us/entra/identity/app-provisioning/use-scim-to-provision-users-and-groups) — Microsoft Learn, 2026-09-15. Checked 2026-10-16.
[^ms-risk]: [What are risk detections?](https://learn.microsoft.com/en-us/entra/id-protection/concept-identity-protection-risks) — Microsoft Learn, 2026-04-22. Checked 2026-10-16.
[^ms-revoke]: [user: revokeSignInSessions](https://learn.microsoft.com/en-us/graph/api/user-revokesigninsessions) — Microsoft Graph v1.0, 2024-04-17. Checked 2026-10-16.
[^ms-revoke-access]: [Revoke user access in an emergency in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/users/users-revoke-access) — Microsoft Learn, 2026-06-19. Checked 2026-10-16, re-checked 2026-10-17.
[^ms-tokens]: [Configurable token lifetimes in the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/configurable-token-lifetimes) — Microsoft Learn, 2026-04-08 (updated 2026-06-15). Checked 2026-10-17.
[^ms-cae]: [Continuous access evaluation](https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation) — Microsoft Learn, 2026-04-08. Checked 2026-10-16.
[^ms-tap]: [Configure Temporary Access Pass](https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass) — Microsoft Learn, 2026-03-04. Checked 2026-10-16.
[^ms-inactive]: [How to manage inactive user accounts](https://learn.microsoft.com/en-us/entra/identity/monitoring-health/howto-manage-inactive-user-accounts) — Microsoft Learn, 2025-02-21. Checked 2026-10-16.
[^ms-guest]: [Monitor and clean up stale guest accounts](https://learn.microsoft.com/en-us/entra/identity/users/clean-up-stale-guest-accounts) — Microsoft Learn, 2024-12-30. Checked 2026-10-17.
[^ms-connect]: [Microsoft Entra Connect: Prerequisites and hardware](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-install-prerequisites) — Microsoft Learn, 2026-01-16. Checked 2026-10-17.
[^ms-gmsa]: [Group Managed Service Accounts overview](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/group-managed-service-accounts/group-managed-service-accounts/group-managed-service-accounts-overview) — Microsoft Learn, 2025-07-01. Checked 2026-10-17.
[^ms-emerg]: [Manage emergency access admin accounts](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access) — Microsoft Learn, 2026-06-04. Checked 2026-10-16.
[^ms-itdr]: [Microsoft Defender for Identity dashboard](https://learn.microsoft.com/en-us/defender-for-identity/dashboard) — Microsoft Learn, 2026-07-02. Checked 2026-10-16.
[^okta-scim]: [Understanding SCIM](https://developer.okta.com/docs/concepts/scim/) and [SCIM 2.0 protocol guide](https://developer.okta.com/docs/api/openapi/okta-scim/guides/scim-20) — Okta Developer. Checked 2026-10-16.
[^okta-api]: [Okta Management API OpenAPI file, version 2026.41.0](https://raw.githubusercontent.com/okta/okta-management-openapi-spec/master/dist/current/management-minimal.yaml) — Okta (operations "Revoke all user sessions", "Deactivate a user", "Suspend a user"); rendered as [User Sessions](https://developer.okta.com/docs/api/openapi/okta-management/management/tags/usersessions) and [User Lifecycle](https://developer.okta.com/docs/api/openapi/okta-management/management/tags/userlifecycle). Checked 2026-10-17.
[^docker-scim]: [Migrate JIT to SCIM](https://docs.docker.com/enterprise/security/provisioning/scim/migrate-scim/) — Docker Docs. Checked 2026-10-16.
[^aws-credrep]: [Get credential reports for your AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_getting-report.html) — AWS IAM User Guide. Checked 2026-10-16.
[^aws-cli-gcr]: [get-credential-report](https://docs.aws.amazon.com/cli/latest/reference/iam/get-credential-report.html) — AWS CLI Command Reference. Checked 2026-10-16.
[^aws-rlu]: [RoleLastUsed](https://docs.aws.amazon.com/IAM/latest/APIReference/API_RoleLastUsed.html) — AWS IAM API Reference. Checked 2026-10-17.
[^aws-root]: [Root user best practices for your AWS account](https://docs.aws.amazon.com/IAM/latest/UserGuide/root-user-best-practices.html) — AWS IAM User Guide. Checked 2026-10-17.
[^aws-bp]: [Security best practices in IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) — AWS IAM User Guide. Checked 2026-10-16.
[^curl]: [curl man page, `--fail-with-body`](https://curl.se/docs/manpage.html) — curl project ("Added in 7.76.0"). Checked 2026-10-17.
[^jasper]: [Jasper Sleet: North Korean remote IT workers' evolving tactics to infiltrate organizations](https://www.microsoft.com/en-us/security/blog/2025/06/30/jasper-sleet-north-korean-remote-it-workers-evolving-tactics-to-infiltrate-organizations/) — Microsoft Threat Intelligence, 2025-06-30 (updated 2026-04-21). Checked 2026-10-17.
[^msrc-mb]: [Microsoft Actions Following Attack by Nation State Actor Midnight Blizzard](https://www.microsoft.com/en-us/msrc/blog/2024/01/microsoft-actions-following-attack-by-nation-state-actor-midnight-blizzard) — MSRC, 2024-01-19. Checked 2026-10-16.
[^ms-mb]: [Midnight Blizzard: Guidance for responders on nation-state attack](https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/) — Microsoft Security Blog, 2024-01-25. Checked 2026-10-16.
[^okta-rca]: [Unauthorized Access to Okta's Support Case Management System: Root Cause and Remediation](https://sec.okta.com/articles/2023/11/unauthorized-access-oktas-support-case-management-system-root-cause/) — Okta Security, 2023-11-03. Checked 2026-10-16.
[^cf]: [Thanksgiving 2023 security incident](https://blog.cloudflare.com/thanksgiving-2023-security-incident/) — Cloudflare, 2024-02-01. Checked 2026-10-16.
[^mandiant]: [UNC5537 Targets Snowflake Customer Instances for Data Theft and Extortion](https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion) — Mandiant (Google Cloud), 2024-06-10. Checked 2026-10-16.
[^cisa-ss]: [AA23-320A, Scattered Spider](https://www.cisa.gov/news-events/cybersecurity-advisories/aa23-320a) — CISA and partners, 2023-11-16, last revised 2025-07-29. Checked 2026-10-16.
[^ncsc]: [Incidents impacting retailers – recommendations from the NCSC](https://www.ncsc.gov.uk/blog-post/incidents-impacting-retailers) — NCSC (UK), 2025-05-04. Checked 2026-10-16.

**Further reading:** [Passkeys](https://fidoalliance.org/passkeys/) (FIDO Alliance);
[Microsoft Entra ID Governance overview](https://learn.microsoft.com/en-us/entra/id-governance/identity-governance-overview).

**Changelog:** 2026-10-16 — first version: research, ledger, draft, six diagrams; Labs 1 and 3 executed on synthetic data, Lab 2
syntax-checked only; no independent fact-check review yet. 2026-10-17 — revised after independent review r1: recovery no longer
trusts phone call-backs (SIM swap); Entra deletion behaviour corrected; proofing before bootstrap; provisioning preconditions and
latency; ATT&CK, CSF and case wording; Lab 2 hardened; figures 2–4 corrected; repetition cut; 11 sources added, 1 removed. 2026-10-17 — revised after independent review r2 (12 minor
findings): access-token lifetime stated as 60–90 minutes with Microsoft's token-lifetime page added; IR 8587 authorship; shared-signals
dating; quarantine mechanism; Okta `forgetDevices` side effect in Lab 2 and the runbook; Lab 1 detects duplicate accounts (re-executed);
policy YAML schema; deprovisioning-coverage formula; dormant-sweep exclusions and NHI cadence; guest-report licence; figure 4 note;
ephemeral term-status sources archived. 2026-10-17, post-score polish: AAL1 glossary wording ("at least one factor"); the
PSTN risk-indicator rule labelled as applied by analogy; Entra's unassign-then-push path for urgent leavers with its caveats;
re-check cadence for product facts; figure 1 edges carry handoff labels; figure 3 reconciliation inputs named in the node instead of on curved
edges; NIST and Entra sources re-opened. 2026-10-17, after the polish check: the urgent-leaver path labelled as this page's
reading, with its trade-off and two preconditions.
