How to Use PowerShell to Retrieve Permissions for Entra ID Apps

Keeping an Eye on App Permissions

The recent Midnight Blizzard attack on Microsoft corporate email accounts emphasized the world of threat that exists today. If attackers can compromise the world’s biggest software company, repeat with thousands of skilled security professionals, what hope have the rest of us? Even if attackers might consider your tenant to be unworthy of their attention, the answer lies in paying attention to what happens in the tenant.

In the past, I’ve written about checking consent grants to apps for high-priority permissions. The script used in that article posts the results to a team channel in the hope that administrators read and respond to anything untoward. Recent events make it useful to discuss how to retrieve the set of permissions held by apps.

Hash Tables for Fast Permission Lookup

To find consents for high-priority permissions (like Mail.Send or Exchange.ManageAsApp), the script builds a set of hash tables to hold the role identifiers and their display names. For instance, this code builds a hash table of all Microsoft Graph roles (also called permissions or scopes):

$GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$GraphRoles = @{}
ForEach ($Role in $GraphApp.AppRoles) { $GraphRoles.Add([string]$Role.Id, [string]$Role.Value) }

$GraphRoles

Name                           Value
----                           -----
b0c13be0-8e20-4bc5-8c55-963c2… TeamsAppInstallation.ReadWriteAndConsentForTeam.All
926a6798-b100-4a20-a22f-a4918… ThreatSubmissionPolicy.ReadWrite.All
c2667967-7050-4e7e-b059-4cbbb… CustomAuthenticationExtension.ReadWrite.All
(etc.)

The script uses separate hash tables for Graph API permissions, Teams permissions. Entra ID permissions, Exchange Online permissions, and so on.

Having hash tables containing role identifiers and names makes it very easy to resolve the roles assigned to registered apps or the service principals used for enterprise apps. As you’ll recall, an enterprise app is an app created by Microsoft or an ISV that is preconfigured to authenticate against Entra ID and is capable of being used in any tenant. When an enterprise app is installed in a tenant, its service principal becomes the tenant-specific instantiation of the app and holds the permissions assigned to the app.

Finding App Permissions from an App’s Service Principal

To find the permissions assigned to an app, we find the service principal identifier for the app and use it to fetch the set of role assignments. Each role assignment allows the use of a Graph API permission or a administrative role (like Exchange.ManageAsApp) allowing the app to behave as if it was an administrator account holding the role:

$App = Get-MgApplication | Where DisplayName -match 'My registered app'
$SP = Get-MgServicePrincipal -All | Where-Object AppId -match $App.Appid [array]$AppRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -All -ServicePrincipalId $SP.id

A role assignment looks like this:

AppRoleId            : 5b567255-7703-4780-807c-7be8301ae99b
CreatedDateTime      : 05/04/2023 16:55:55
DeletedDateTime      :
Id                   : HkiavhziPkSBG_2Lh0ibAx1voO6aUiJCm3NQwDYWeu8
PrincipalDisplayName : My registered app
PrincipalId          : be9a481e-e21c-443e-811b-fd8b87489b03
PrincipalType        : ServicePrincipal
ResourceDisplayName  : Microsoft Graph
ResourceId           : 14a3c489-ed6c-4005-96d1-be9c5770f7a3
AdditionalProperties : {}

We’re interested in the resource identifier (ResourceId) and AppRoleId properties. Together, these tell us the resource (like the Microsoft Graph or SharePoint Online) and role that the permission relates to. Because an app can hold many permissions, we loop through the array to retrieve each permission:

ForEach ($AppRoleAssignment in $AppRoleAssignments) {
  $Permission = (Get-MgServicePrincipal -ServicePrincipalId `
  $AppRoleAssignment.resourceId).appRoles | `
  Where-Object id -match $AppRoleAssignment.AppRoleId | Select-Object -ExpandProperty Value
  Write-Host ("The app {0} has the permission {1}" -f ` 
  $AppRoleAssignment.PrincipalDisplayName, $Permission)
}

The advantage of the hash tables is that the script doesn’t need to keep running the Get-MgServicePrincipal cmdlet to fetch the set of app roles owned by a resource. Thus, we can simply say:

$Permission = $GraphRoles[$AppRoleAssignment.AppRoleId]

Other Methods to Report App Permissions

As is often the case with PowerShell, other methods exist to resolve the names of app permissions from role identifiers. To explore the possibilities, I suggest you look at Sean Avinue’s article about how to generate a risk report or Vasil Michev’s application service principal inventory script.

One of the great things about PowerShell is the ease of repurposing code written by other people to expand and enhance functionality. In this context, “ease” means that it is easier to reuse code than write it from scratch. Some effort is often necessary to shoehorn the code into your scripts.

Vasil’s inventory script handles both delegate and application permissions. As an example of what I mean about code reuse, I took some of his code and fitted it into my script to generate a report about expiring app credentials (secrets and certificates). Figure 1 shows the output email with the high-priority permissions asterisked. Perhaps having highly-permissioned apps brought to the attention of administrators on a regular basis will force them to review what’s happening with apps.

Email containing an app permissions report.
Figure 1: Email containing an app permission report

You can download the updated script from GitHub. Happy coding!


Learn more about how the Microsoft 365 applications and the Graph really work on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.