Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure Microsoft Entra Privileged Identity Management to enforce just-in-time role activation, approval workflows, and access reviews for Azure AD privileged roles.
.claude/skills/implementing-azure-ad-privileged-identity-management/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
Microsoft Entra Privileged Identity Management (PIM) provides time-based and approval-based role activation to mitigate risks from excessive, unnecessary, or misused access to critical resources. PIM replaces permanent (standing) privilege assignments with eligible assignments that require users to explicitly activate their role before use, with configurable duration, MFA enforcement, approval workflows, and justification requirements. This is a core component of Zero Trust identity governance in Microsoft environments.
| Type | Behavior | Use Case | |------|----------|----------| | Eligible | User must activate the role before use; expires after configured duration | Day-to-day admin work | | Active | Role is always active; no activation needed | Service accounts, break-glass accounts | | Time-Bound | Either type with explicit start/end dates | Temporary project access, contractor access |
User with Eligible Assignment
│
├── Opens PIM portal → My Roles
│
├── Clicks "Activate" on the desired role
│
├── Provides justification and optional ticket number
│
├── Completes MFA challenge (if required)
│
├── [If approval required] → Notification sent to approvers
│ │
│ ├── Approver reviews and approves/denies
│ └── User notified of decision
│
├── Role activated for configured duration (e.g., 8 hours)
│
└── Role automatically deactivated when duration expiresAudit current permanent role assignments and determine which should be converted to eligible:
| Current Role | Permanent Holders | Action | |-------------|-------------------|--------| | Global Administrator | 2-3 admins | Convert to eligible, keep 1 break-glass active | | Exchange Administrator | IT team | Convert all to eligible | | Security Administrator | SOC team | Convert to eligible | | User Administrator | Help desk | Convert to eligible | | Application Administrator | DevOps | Convert to eligible |
Best practice: Maintain no more than 2 permanent Global Administrators (break-glass accounts).
For each Entra directory role, configure PIM settings:
Via Microsoft Entra Admin Center:
Activation Settings:
Assignment Settings:
Notification Settings:
pythonimport requests # Acquire token for Microsoft Graph def get_graph_token(tenant_id, client_id, client_secret): url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" data = { "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, "scope": "https://graph.microsoft.com/.default" } response = requests.post(url, data=data) return response.json()["access_token"] # Create eligible role assignment def create_eligible_assignment(token, role_definition_id, principal_id, directory_scope="/", duration_hours=8): url = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleRequests" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } body = { "action": "adminAssign", "justification": "PIM eligible assignment", "roleDefinitionId": role_definition_id, "directoryScopeId": directory_scope, "principalId": principal_id, "scheduleInfo": { "startDateTime": "2025-01-01T00:00:00Z", "expiration": { "type": "afterDuration", "duration": "P180D" # 180-day eligible window } } } response = requests.post(url, headers=headers, json=body) return response.json() # Activate a role (user self-service) def activate_role(token, role_definition_id, principal_id, justification, duration_hours=8): url = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleRequests" headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json" } body = { "action": "selfActivate", "principalId": principal_id, "roleDefinitionId": role_definition_id, "directoryScopeId": "/", "justification": justification, "scheduleInfo": { "startDateTime": None, # Now "expiration": { "type": "afterDuration", "duration": f"PT{duration_hours}H" } } } response = requests.post(url, headers=headers, json=body) return response.json()
Set up recurring access reviews to verify eligible assignments remain appropriate:
Enable PIM security alerts:
| Alert | Trigger | Action | |-------|---------|--------| | Too many global admins | > 5 Global Admins | Review and reduce | | Roles being assigned outside PIM | Direct role assignment | Investigate and convert to PIM | | Roles not requiring MFA | Activation without MFA | Enable MFA requirement | | Stale eligible assignments | Not activated in 90 days | Review and potentially remove | | Potential stale service accounts | Active assignments not used | Investigate and decommission |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.