To review app permissions in Microsoft Entra ID, list the delegated permission grants and the application permissions granted to each app, then look for broad permissions an app does not need. Graph returns what has been consented to, not what an app asks for, and the admin centre shows 1 application at a time.
| Method | Best fit |
|---|---|
| PowerShell | A tenant-wide CSV of delegated grants and application permissions on Microsoft Graph, broad ones first. |
| Admin centre | Checking what 1 application has been granted, by administrators and by users. |
| Altivane | Recommended for recurring reviews, with applications holding high-risk permissions rated against a threshold. |
Before you start
Use an account in a role that can read grants, such as Global Reader, Directory Readers or Application Administrator. The PowerShell example requests Directory., the least privileged permission for listing delegated grants and enough for its other calls; your organisation may require administrator consent. The admin centre steps need Cloud Application Administrator or Application Administrator.
The examples show permissions that have been granted, so one an app requests but nobody has approved is left out, and a recent grant can be slow to appear. The script reads application permissions on Microsoft Graph only, so those on other APIs, such as Exchange Online or SharePoint, are not in the CSV; delegated grants cover every API. Access can also come from Entra roles, Exchange RBAC for applications or Teams resource-specific consent, which are not read here. The examples read and export; they do not grant or revoke anything.
Option 1
Review app permissions with PowerShell
Install the modules once, then connect and export every delegated grant and every application permission on Microsoft Graph.
# Install once, if the modules are not already installed.
$modules = 'Microsoft.Graph.Authentication', 'Microsoft.Graph.Applications',
'Microsoft.Graph.Identity.SignIns'
Install-Module -Name $modules -Scope CurrentUser$ErrorActionPreference = 'Stop'
$graphAppId = '00000003-0000-0000-c000-000000000000'
$wide = '\.(ReadWrite|FullControl)\.All$'
Connect-MgGraph -Scopes 'Directory.Read.All' -NoWelcome
try {
# Names of every service principal, looked up by object ID.
$names = @{}
Get-MgServicePrincipal -All -Property 'id', 'displayName' |
ForEach-Object { $names[$_.Id] = $_.DisplayName }
# Microsoft Graph's own service principal, for the names of its application permissions.
$graph = Get-MgServicePrincipal -Filter "appId eq '$graphAppId'" -Property 'id', 'appRoles'
$roles = @{}
foreach ($role in $graph.AppRoles) { $roles[[string]$role.Id] = $role.Value }
$rows = [System.Collections.Generic.List[object]]::new()
# Every app granted an application permission on Microsoft Graph.
$assigned = Get-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $graph.Id -All
$apps = @($assigned | Where-Object { $_.PrincipalType -eq 'ServicePrincipal' })
foreach ($item in $apps) {
$permission = $roles[[string]$item.AppRoleId]
if (-not $permission) { $permission = [string]$item.AppRoleId }
$rows.Add([pscustomobject]@{
Application = $item.PrincipalDisplayName
Kind = 'Application permission'
Resource = $item.ResourceDisplayName
Permission = $permission
UserId = ''
Wide = $permission -match $wide
})
}
# Every delegated grant, one row per permission.
foreach ($grant in Get-MgOauth2PermissionGrant -All) {
$admin = $grant.ConsentType -eq 'AllPrincipals'
$kind = if ($admin) { 'Delegated, admin consent' } else { 'Delegated, user consent' }
$client = $names[$grant.ClientId]
if (-not $client) { $client = $grant.ClientId }
$resource = $names[$grant.ResourceId]
if (-not $resource) { $resource = $grant.ResourceId }
foreach ($scope in @($grant.Scope -split ' ' | Where-Object { $_ })) {
$rows.Add([pscustomobject]@{
Application = $client
Kind = $kind
Resource = $resource
Permission = $scope
UserId = $grant.PrincipalId
Wide = $scope -match $wide
})
}
}
$report = @($rows | Sort-Object @{ Expression = 'Wide'; Descending = $true },
Application, Kind, Permission)
if ($report.Count -eq 0) {
Write-Output 'No application permissions or delegated grants were found.'
} else {
$report | Export-Csv -Path './app-permissions.csv' -NoTypeInformation -Encoding utf8
$flagged = @($report | Where-Object { $_.Wide }).Count
Write-Output "$($report.Count) permissions exported; $flagged are marked Wide."
}
} finally {
Disconnect-MgGraph | Out-Null
}The CSV has 1 row per permission per app, broad ones first. Kind says how it was granted: an application permission, a delegated one an administrator consented to for everyone, or one a single user agreed to, whose object ID is in UserId. Wide is true for names ending in ReadWrite. or FullControl., which allow changes across the tenant. It is a rough first filter, not Microsoft's rating, and passes over broad read permissions such as Mail..
Check a single app
Reconnect with Connect-MgGraph -Scopes 'Directory., then read 1 app's grants. The output shows each API and permission as an ID. This takes 3 calls per app, so the export above works from the Microsoft Graph side instead.
# Run while connected to Microsoft Graph. Replace the example app name.
$filter = "displayName eq 'Contoso Payroll'"
$sp = Get-MgServicePrincipal -Filter $filter -Property 'id', 'displayName'
Get-MgOauth2PermissionGrant -Filter "clientId eq '$($sp.Id)'" |
Select-Object ConsentType, PrincipalId, ResourceId, Scope
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id |
Select-Object ResourceDisplayName, AppRoleIdOption 2
Review app permissions in the admin centre
- Sign in to the Microsoft Entra admin centre for the correct tenant.
- Go to Entra ID > Enterprise apps > All applications and select the application.
- Select Permissions.
- Open the Admin consent tab for the permissions granted to the whole organisation, and the User consent tab for those granted to a specific user or group.
- Select a permission to open the Permission Details pane. Leave Revoke permission alone: it removes the grant in the tenant.
This suits 1 application you already have in mind. These steps show 1 application at a time, so for every app together, export with PowerShell or a report.
Option 3 ยท Recommended for recurring reviews
Report on app permissions with Altivane
- Open Altivane and choose the tenant.
- Build the tenant's documents with the Applications scorecard report in full, so the lists are included with the counts; builds start it on Summary, which gives counts without lists. Include Microsoft's own enterprise applications if you want them in the results; they are left out otherwise.
- Open the tenant's Excel workbook and read High-Privilege Applications for apps holding permissions on Altivane's high-risk list, plus Granted Application Permissions and Delegated Permission Grants for every grant.
- Compare Application Permissions Requested and Delegated Permissions Requested in App Registrations with Application Permissions Granted.
Altivane reads the application permissions of every app on every API, not Microsoft Graph alone. Consent Type separates admin consent for all users from user consent, with the User who agreed. Only application permissions and admin-consented delegated grants count towards High-Privilege Applications, and the high-risk list includes broad read permissions such as Mail. as well as write ones. The scorecard rates the number of those applications, with Warning at 10 and Critical at 25 to start. The two permission tables start as Excel only; they can be included in the Word report too. Where delegated grants cannot be read, the figures say Not read rather than 0. The build also writes the tenant's Word report.
Common questions
Why is a permission my app requests not in the results?
Only consent counts here. A registration lists what it asks for, and a request nobody has approved does not appear in the grants.
Why does the list include apps nobody here installed?
The delegated grants cover every app in the tenant, Microsoft's own services included, and users can consent for themselves in some cases. Filter Kind to the user consent rows and read UserId to see who agreed.
Who decides whether an app keeps a permission?
The app's owners. Altivane lists registrations with no owner in App Registrations Without Owners and shows each high-privilege application's last sign-in when Microsoft reports one. Credentials are the other half of an app review: see how to find expiring app secrets.
Microsoft references
Technical references checked on 5 October 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.
- List oauth2PermissionGrants in Microsoft Graph
- oAuth2PermissionGrant properties
- List appRoleAssignments granted for a service principal
- appRoleAssignment properties
- Get-MgOauth2PermissionGrant reference
- Get-
Mg Service Principal App Role Assigned To reference - Get-
Mg Service Principal App Role Assignment reference - Grant or revoke API permissions programmatically
- Review permissions granted to enterprise applications