One of the common types of questions that people post over at Q&A and other communities revolves around the line of “why is my cmdlet/script/app getting an access denied error”. While there can be multiple reasons for that, usually the first thing to check is the (decoded) access token and the claims therein. This gives us a rough idea of what permissions the current user (or application) has, but sometimes it is insufficient for finding the underlying cause. So in this article, let us explore another method we can use, namely the estimateAccess endpoint.
Before we get started, an important note: the estimateAccess endpoint/method is undocumented and unsupported one. It is regardless used across the Entra portal UI, so it’s relatively safe to use. Still, there is always the possibility that it suddenly stops working, so don’t say you weren’t warned 🙂
Short refresher on some definitions
The estimateAccess method works by “comparing” the privileges granted to the user/application that calls it against a set of resource actions and resource scopes provided via the JSON payload. In case I just hit you with an incomprehensible wall of terms, let’s start with a short refresher.
Within the unified role management API, we have support for several providers, such as Microsoft Entra or Exchange Online. Providers in turn can have multiple namespaces, but most importantly for our discussion, have a set of resource actions. Each resource action represents an operation that you can perform against a given entity (object), such as changing the user’s password or assigning licenses.
For example, the Manage user licenses action is defined as microsoft.directory/users/assignLicense. From left to right, we have microsoft.directory for the resource namespace, users for the entity (object) type, and assignLicense for the operation allowed by the action. In turn, each role definition for a provider includes a set of allowedResourceActions, effectively listing what operations will be allowed for a user or application assigned the role.
Let’s take a look at another example. The microsoft.office365.supportTickets/allEntities/allTasks action allows the assigned user or application to perform all tasks across all entities supported for the microsoft.office365.supportTickets namespace. In other words, it allows you to manage Office 365 support tickets. Similarly, the microsoft.azure.supportTickets namespace features the microsoft.azure.supportTickets/allEntities/allTasks action. In turn, any “support” role includes them both, i.e. if we check the definition of the Service Support Administrator Entra role:
GET https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions/f023fd81-a637-4b56-95fd-791ac0226033?$select=id,displayName,rolePermissions
With all that in mind, here is how to get the list of all resource actions for the microsoft.directory resource namespace of the Microsoft Entra provider. Or in other words all the resource actions supported by estimateAccess.
#Fetch all resource actions supported by microsoft.directory GET https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/microsoft.directory/resourceActions?$select=name&$top=999 #Fetch all resource actions supported by microsoft.directory via the Graph SDK $Actions = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/roleManagement/directory/resourceNamespaces/microsoft.directory/resourceActions?`$select=name&`$top=999" | select -ExpandProperty value $Actions.Count 702 $Actions | ? {$_.Values -match "/users/([^\.])*password.*"} | select -ExpandProperty Values microsoft.directory/users/changePassword microsoft.directory/users/passwordPolicies/update microsoft.directory/users/password/update
Note that this operation requires RoleManagement.Read.Directory permissions, albeit this only applies to the example above and is not needed for actual use of estimateAccess.
Using the estimateAccess method
OK, after this long introduction, let’s actually see the estimateAccess method in action. As mentioned above, we will need to provide a JSON payload with the resource action and scope we want to check access against. Multiple such combinations can be included in a single request. There are no specific permission requirements for calling the endpoint, but the access token we pass must be for the Graph resource (i.e. https://graph.microsoft.com). Lastly, this is a POST request. Here’s an example of calling estimateAccess using the Graph explorer tool:
POST https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess {"resourceActionAuthorizationChecks":[{"directoryScopeId":"/", "resourceAction":"microsoft.directory/users/basic/update"}]}
The important part of the response is the accessDecision facet, which tells us that the calling user has sufficient permissions to use the microsoft.directory/users/basic/update resource action. In other words, the user will be able to perform changes to basic properties on user objects within the directory. Do note it doesn’t necessarily tell us that he can update all users, but we will talk more about this in a minute.
Here is how to perform the same request via the Graph SDK for PowerShell. As this is an unsupported endpoint, there is no matching cmdlet, but we can simply leverage Invoke-MgGraphRequest and pass the corresponding URI and payload:
$body = @{ "resourceActionAuthorizationChecks" = @(@{ "directoryScopeId" = "/" "resourceAction" = "microsoft.directory/users/basic/update" }) } | ConvertTo-Json -Compress Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" -Body $body | select -ExpandProperty value Name Value ---- ----- missingClaims {} directoryScopeId / accessDecision allowed resourceAction microsoft.directory/users/basic/update
Both examples above use the delegate permissions flow and check whether the currently authenticated user has access to the action in question. The application permissions scenario works virtually the same, the only difference being the access token provided. But we can use it as an example on how to query the estimateAccess endpoint via direct HTTPS requests:
$body = @{ "resourceActionAuthorizationChecks" = @(@{ "directoryScopeId" = "/" "resourceAction" = "microsoft.directory/users/basic/update" }) } | ConvertTo-Json -Compress $uri = "https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess" $res = Invoke-WebRequest -Method Post -Uri $uri -Headers $authHeader -Body $body -ContentType "application/json" ($res.Content | ConvertFrom-Json).value directoryScopeId resourceAction accessDecision missingClaims ---------------- -------------- -------------- ------------- / microsoft.directory/users/basic/update notAllowed {}
Unlike the delegate permission scenarios above, which were both run in the context of my own user, a Global administrator, this time around the response tells us that the calling principal is NOT allowed access to the resource action. Indeed, the call was made via an access token for an application that only has read scopes granted, so this is the expected result.
Leveraging the directoryScopeId parameter, we can perform checks against individual directory object, say a given user or a group. This is in fact the preferred method, as functionalities like scoped role or restricted management administrative units have a say in the process of determining the effective access you get, thus the unscoped query will not always give you the full picture. Which is also probably why we have the word “estimate” in the method’s name. Returning to our initial query, if we replace the scope to one corresponding to a user within the scope of a RMAU, we get:
POST https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess {"directoryScopeId":"/8daf0dd0-12e4-436b-b5a0-615bd9c3630a", "resourceAction":"microsoft.directory/users/basic/update"}
Here is also an example of performing multiple checks via a single request:
POST https://graph.microsoft.com/beta/roleManagement/directory/estimateAccess { "resourceActionAuthorizationChecks": [ {"directoryScopeId":"/e4252b9e-4beb-4e9c-b1ff-45ddf64deb24", "resourceAction":"microsoft.directory/users/basic/update"}, {"directoryScopeId":"/8daf0dd0-12e4-436b-b5a0-615bd9c3630a", "resourceAction":"microsoft.directory/users/basic/update"} ] }
Some additional notes
The examples above should be sufficient to illustrate how to leverage the estimateAccess method, and you can also “borrow” some of the code the Entra portal uses to query it. But as we currently lack any documentation on the method/endpoint, it is likely that you will run into some limitations or caveats, which I was too lazy to test. Still, the method can prove useful.
From what I can tell, there seems to be limited support for this method across the various providers supported by the unified role management API. In fact, the Microsoft Entra provider seems to be the only one that supports it. In addition, only some of the 69 namespaces available for said provider support it. Granted, the most important of these, microsoft.directory is supported but even with the 702 resource actions available within, not every operations on users, groups and other directory objects will be covered.
In fact, the only other namespace that seems to support the estimateAccess method is the microsoft.people one, and I was only able to discover this because I captured the Entra portal network trace. Frankly, I am too lazy to test all the 69 currently supported namespaces, but out of the dozen or so I tried, none had actions that are supported (the accessDecision value returned by the query was invalidAction).