☁️Entra IDPrivileged AccessPermissionsApplications

Entra ID custom directory roles unused admin roles audit

Audit Entra custom roles, unassigned enabled roles, and admin counts. Verify Graph evidence, reduce permissions, and govern access with PIM reviews.

Younes AZABARBy Younes AZABAR14 min read
Entra ID custom directory roles unused admin roles audit

Entra ID Custom Directory Roles Unused Admin Roles Audit: What Is Being Measured?

An entra id custom directory roles unused admin roles audit should answer three separate questions: which permission definitions exist, which principals hold them, and whether those assignments still serve a business need. Microsoft models an assignment as a connection between a definition, a principal, and a scope. Finding a custom definition alone does not demonstrate privileged access. Microsoft Graph: unifiedRoleAssignment.

Access reviews address assignments; a definition audit separately examines the permissions those assignments grant. A review can miss assignments outside its selected roles, populations, or assignment types. A properly targeted review can remove access when its results are applied. Reviewing assignments also leaves a separate question: should the underlying definition still exist, and are its permissions appropriate? Create role access reviews, complete and apply reviews.

This guide separates those questions, provides documented Graph requests, and explains seven etc-collector checks. It contains no tenant results or executed Graph operations. The practical objective is an evidence pack that lets an administrator justify retained access, remove unnecessary assignments, and retire genuinely redundant custom definitions without confusing those actions.

Definitions, enabled roles, and holders

In Graph, isBuiltIn=false identifies a custom role definition. isEnabled describes whether the definition is available for assignment; it does not say whether somebody currently holds it. rolePermissions contains its permission collection. Microsoft marks resourceScopes as not to be used and planned for deprecation; attach scope to the assignment instead. unifiedRoleDefinition properties.

Microsoft Entra directory roles govern Entra resources; Azure resource RBAC governs resources such as virtual machines and storage. Their permissions are not interchangeable. Custom directory roles can implement a narrower job function than a built-in role, so “custom” is not itself a security verdict. Microsoft Entra RBAC overview.

For this audit, maintain three findings categories: definition complexity, assignments requiring justification, and collection uncertainty. This is a recommended reporting method. It prevents an empty assignment list from being presented as an active compromise and prevents an apparently small role catalogue from substituting for permission analysis.

How It Works: Permissions and Scope Determine Exposure

A permission name follows a namespace, entity, property set, and action. For example, microsoft.directory/applications/credentials/update is a directory permission, not a consented Graph application permission named Application.ReadWrite.All. Microsoft's privileged-permissions documentation explains the permission structure and how privileged permissions can make a built-in or custom role privileged. Privileged roles and permissions.

The application-registration permission catalogue identifies credential updates as changes to certificates and secrets on single-tenant and multitenant applications. Its applications.myOrganization variant restricts the application subtype. It also distinguishes applications/permissions/update, which changes permission configuration, from actually granting consent. Preserve these differences when comparing definitions; similar-looking action names are not interchangeable. Custom application-registration permissions.

A conditional credential abuse path

Microsoft warns that an administrator able to add application credentials may impersonate that application, whose permissions can exceed the administrator's own. The following is an operational inference from that warning and the custom-role scope model: an effective credential-update assignment at directory scope can expose app registrations in that directory; an assignment limited to one registration constrains the target accordingly. Delegate application-management permissions.

The inferred chain needs an effective assignment or permitted activation, a credential accepted for the targeted application, and useful effective application permissions. It does not automatically grant Global Administrator. It does not demonstrate control over every workload identity: a local app registration, a managed identity, and a service principal for an externally owned application are different objects. Application-management delegation, RBAC and workload identities.

A definition-count detector cannot establish this chain. PA_CUSTOM_ROLE_EXCESSIVE tests the number of non-built-in roles, not their actions or assignment scopes (azure/privileged-access/roles/custom-role-excessive.go:24). A single powerful definition can therefore deserve investigation even when that count check is clear.

Named administrator roles remain separate populations

Application Administrator concerns application administration; Exchange Administrator and SharePoint Administrator govern their respective services. Security Administrator manages security configuration, while Privileged Role Administrator manages role assignments and PIM. These are distinct responsibilities in Microsoft's role reference, not five names for the same privilege. Built-in role reference.

Recommend asking each service owner to explain the tasks requiring its role, the required scope, and the expected access period. Keep non-human holders visible: our related article on service principals with administrative roles and standing privilege examines holder ownership and lifecycle. That complements a definition inventory; it does not replace one.

Measurement: Documented Microsoft Graph Requests

The requests below are copied from the cited Learn HTTP examples or request syntax. Each has its own code block. They are documentation, not instructions executed against a tenant for this article. In an authorized audit, record collection time, permissions, response status, and pagination. Follow every returned @odata.nextLink; classify failed or incomplete collection as unknown, not as zero assignments.

1. List definitions and classify custom roles

Learn's directory-provider example lists role definitions:

GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions

The same API documents equality filtering on isBuiltIn. For the request shown, classify returned definitions locally using isBuiltIn=false, retain their IDs and enabled state, and inspect the permission collections. RoleManagement.Read.Directory is a supported read permission; delegated calls also require an appropriate operator role. List roleDefinitions.

Recommend preserving the raw definition beside your interpretation. A display name such as “Application Support” does not explain which write actions it permits. Put the exact actions, business owner, and justification in the audit record so a future reviewer can compare permissions rather than names.

2. Distinguish activated built-in roles

The documented directoryRoles example is:

GET https://graph.microsoft.com/v1.0/directoryRoles

This operation returns activated roles, not the entire set of available built-in definitions. Directory-role activation in this API is also not a user's PIM activation session. Reconcile roleTemplateId where appropriate rather than treating instance IDs and definition IDs as interchangeable. List directoryRoles.

3. Collect active assignments

Learn documents the directory-provider request syntax:

GET /roleManagement/directory/roleAssignments

Use the v1.0 service root for that relative path. Learn also supplies this exact filter-and-expand example:

GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?$filter=roleDefinitionId eq '62e90394-69f5-4237-9190-012177145e10'&$expand=principal

The GUID is Microsoft's Global Administrator example, not a custom definition discovered in your tenant. For a per-definition investigation, substitute the verified definition ID. The list API supports $filter, $expand, and $select, with RoleManagement.Read.Directory among its read permissions. List unifiedRoleAssignments.

Join assignments to definitions using roleDefinitionId, and preserve principalId and directoryScopeId. Supported principals include users, role-assignable groups, and service principals. An expanded group principal is still a group object; it is not a count of the people receiving access through membership. Assignment fields and relationships.

4. Collect PIM eligibility separately

The documented eligibility example is:

GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleInstances

The API returns eligibility instances and supports selection, filtering, and expansion. Its least-privileged read permission is RoleEligibilitySchedule.Read.Directory; delegated access also has an operator-role requirement. List roleEligibilityScheduleInstances.

Recommend keeping active and eligible records separately before producing a combined view. Match principal, definition, scope, and relevant dates. An empty active list cannot settle whether someone remains eligible, and an eligibility response cannot establish historical usage. Record exactly which question each dataset answers.

5. Identify privileged assignments without hiding API version

Learn's privileged-roles page gives this optional example:

GET https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments?$expand=roleDefinition&$filter=roleDefinition/isPrivileged eq true

This is explicitly a beta request. Do not silently rewrite it as v1.0 or imply that isPrivileged is a property documented by the v1.0 definition resource used above. The page also describes the PRIVILEGED label in the admin center. Privileged-role identification.

Detection: Count Assignments, Distinct Principals, and Unknowns

Recommend producing the following worksheet per role. These are audit reporting fields, not additional Microsoft limits:

MeasurementInterpretation
Custom definition countComplexity signal; review exact permissions separately
Active assignment countCurrent active grant records in the captured dataset
Eligible assignment countEligibility records captured independently
Distinct principal IDsUnique assigned identities under the chosen counting rule
Principal typesSeparate users, groups, and service principals
Assignment scopesSeparate directory-wide and narrower grants
Completeness and errorsEvidence that supports, or limits, conclusions

For an enabled role with no collected assignments, first reconcile IDs and confirm both collection paths succeeded. Then ask its owner whether the definition has a planned purpose. Describe it as “enabled with no collected active or eligible assignment,” not “never used.” The collector's UNUSED predicate checks current input membership, not activity history (azure/privileged-access/roles/unused-admin-role.go:36).

For holder counts, state whether your number means assignment records or distinct principals. The shared TOO_MANY detector compares role IDs and counts matching assignment entries; it does not deduplicate people, filter principal type, or restrict scope (azure/privileged-access/roles/too-many-privileged-roles.go:43). Treat its result as a triage signal requiring this worksheet.

Turn a signal into a reviewable decision

Recommend giving each finding an explicit disposition: retain with justification, investigate incomplete evidence, remove an assignment, narrow an assignment, or retire a custom definition. Record the person responsible for the decision and the evidence they examined. Keep the detector result beside that disposition so an operational exception remains distinguishable from a measurement error.

For a definition-count finding, ask for a permission comparison before recommending consolidation. For an UNUSED finding, ask for the active and eligible collection records before recommending retirement. For a TOO_MANY finding, ask which principals and scopes produced the count before recommending a smaller population. These questions follow the three predicates described above; none requires inventing an exploitation result.

Recommend documenting the counting rule in the export itself. If one principal appears at several scopes, explain whether the report counts those assignments separately. If a group appears, label its assignment as a group grant and reserve a separate field for membership evidence. If eligibility could not be collected, mark the absence conclusion provisional. That makes the report usable by a reviewer who did not run the collection.

Choose closure evidence when opening the finding. A definition-retirement task should end with the approved definition absent from a fresh inventory. An assignment-removal task should end with that specific grant absent from the relevant dataset. A retained exception should end with a documented purpose and a future review date. These are recommended acceptance criteria for the audit, not claims about an automatic enforcement feature.

Microsoft recommends fewer than five Global Administrators and fewer than ten privileged role assignments across the organization. These are governance recommendations, not enforced assignment ceilings. The cited page does not establish a two-to-four-holder cap per Application, Exchange, Security, SharePoint, or Privileged Role Administrator role. The collector's per-role thresholds are product defaults. Microsoft Entra role best practices.

Remediation: Retire Redundant Definitions and Govern Remaining Access

1. Decide which custom roles should remain

Recommend reviewing each definition with the service owner: what exact operation needs it, what narrower scope works, and who is accountable for future changes? Consolidate redundant definitions only after comparing their actions and intended assignments. Retain useful custom roles when they implement the required permission set more precisely than a built-in role. Least-privilege role selection.

Treat this as a decision record, not a drive to reach an arbitrary catalogue size. For an apparently empty definition, capture the active and eligible inventories, confirm collection quality, and investigate planned dependencies before approving retirement. Where access remains necessary, prioritize reducing unnecessary credential-management actions and broad application scope. Custom registration permissions.

2. Remove unnecessary assignments, then delete approved custom definitions

Recommend separating the two change decisions. Removing an assignment addresses a holder's access; retiring a definition addresses the role catalogue. Microsoft's delete API supports custom definitions, prohibits deletion of built-in roles, and documents this syntax:

DELETE /roleManagement/directory/roleDefinitions/{id}

This is a cited write operation, not executed here. Replace {id} only with the approved custom definition ID. Learn specifies RoleManagement.ReadWrite.Directory as the least-privileged API permission and Privileged Role Administrator as the least-privileged supported delegated operator role. A successful delete returns 204 No Content. Delete unifiedRoleDefinition.

For a built-in role reported by UNUSED, document why there are no holders and whether the activated instance is still relevant. Do not apply a custom-definition deletion runbook to every UNUSED result. Recommend verifying the post-change definition and assignment inventories before closing the change.

3. Set justified populations and migrate suitable human access to PIM

Recommend a role-by-role access budget based on required duties and operational coverage, with explicit exceptions. Assess the organization's aggregate privileged assignments against Microsoft's guidance rather than treating the collector's thresholds as mandatory Microsoft quotas.

PIM supports time-based and approval-based activation for built-in and custom Entra roles. Its deployment plan recommends testing a pilot, configuring settings for each role, and preserving emergency access. Service principals cannot receive eligible assignments; they can receive time-limited active assignments. Microsoft's plan recommends two cloud-only emergency accounts with permanent Global Administrator assignments. Plan a PIM deployment.

For suitable human assignments, recommend testing approval, MFA, justification, and duration against the intended operating model before rollout. Our article on PIM activation approval, MFA, justification, and duration controls covers that separate configuration review.

Confirm licensing before adopting the plan: custom-role use requires P1 for assigned users, while PIM requires P2 or Governance. Service-principal access reviews need Workload ID Premium in addition to P2 or Governance. Custom-role licensing, PIM licensing and role guidance, PIM review prerequisites.

4. Review access and apply the decisions

In PIM, select the relevant Entra roles and populations, choose active assignments, eligible assignments, or both, and configure responsible reviewers and non-response handling. A review captures a snapshot at its start; role-assignable groups appear without member expansion. Create role access reviews.

Recommend retaining the review scope alongside results so “review completed” cannot be mistaken for “all administrative access reviewed.” Document separate membership evidence where a role is assigned to a group. Give workload owners a review task appropriate to the service identity rather than assuming human activation controls address it.

Denied access is removed when review results are applied, manually or through configured auto-application. Merely recording a denial is not the final revocation step. Complete role access reviews.

5. Verify the outcome with the same measurements

Recommend repeating the documented reads after changes, comparing definition IDs, permission actions, scopes, and active and eligible assignments. Check the specific change you approved: a removed holder, a narrowed permission, or a retired definition. Preserve exceptions and unknowns rather than declaring the audit successful because one count dropped.

How EtcSec Detects This

The following seven IDs exist in the catalogue. Their implementations were read in etc-collector at commit b767fc1ef7ea4cf52d797d5aea1dd4c2d3d07d7c. Paths below are relative to public/internal/audit/detectors/.

DetectorPositive criterionCatalogue severity / weightCode severity
PA_CUSTOM_ROLE_EXCESSIVEMore than 10 non-built-in definitions; first positive at 11Medium / 1Medium
PA_UNUSED_ADMIN_ROLEEnabled role with no matching RoleID in collected assignmentsMedium / 1Medium
PA_TOO_MANY_APP_ADMINSMore than 5 Application Administrator assignment entries; first positive at 6Info / 0High
PA_TOO_MANY_EXCHANGE_ADMINSMore than 3 Exchange Administrator entries; first positive at 4Info / 0High
PA_TOO_MANY_SECURITY_ADMINSMore than 3 Security Administrator entries; first positive at 4Info / 0High
PA_TOO_MANY_SHAREPOINT_ADMINSMore than 3 SharePoint Administrator entries; first positive at 4Info / 0High
PA_TOO_MANY_PRIVILEGED_ROLE_ADMINSMore than 3 Privileged Role Administrator entries; first positive at 4Info / 0Critical

CUSTOM uses azure/privileged-access/roles/custom-role-excessive.go:24; UNUSED uses azure/privileged-access/roles/unused-admin-role.go:36. All five TOO_MANY checks use the shared Detect() in azure/privileged-access/roles/too-many-privileged-roles.go:43. Their individual too-many-*-admins.go constructors set thresholds at line 17 and severities at line 19. Each implementation returns a finding even with Count=0; positive detection requires Count>0. Above threshold, CUSTOM and TOO_MANY return the full matching count, not only the excess.

The five TOO_MANY entries are Info with weight 0 in the catalogue, although their constructors emit High, or Critical for Privileged Role Administrator. This discrepancy is preserved explicitly. Do not silently promote the catalogue score or describe the runtime severities as the catalogue values.

Collection limits affect interpretation

The reviewed provider combines active assignments with collected eligibility using principal, role, and scope (public/internal/providers/azure/client.go:1597, :1626, :1784). Its role inventory combines activated built-in instances with custom definitions, mapping custom definition IDs to RoleTemplateID and setting their internal IsEnabled=true without copying Graph's enabled value (client.go:1500).

Custom-definition reads are best effort; the reviewed role and eligibility paths do not implement nextLink loops. Eligibility HTTP or parsing failures can return an empty map (client.go:404). Consequently, a missing eligibility collection can produce an apparent UNUSED result or undercount TOO_MANY populations. These are static code observations, not reproduced tenant failures.

Close the audit with separate evidence for definition necessity, holder justification, and effective revocation. A clean threshold result is useful, but the accountable decision is whether each retained permission and assignment still has a documented purpose.

Explore the identity security pages that support this topic