How to Report User-Consented Permissions on Applications in Microsoft Entra

By default, users in your organisation can authorise applications to access data about themselves in Microsoft Entra. Not only that, by default, users can also authorise applications to act on their behalf, such as create Microsoft Teams and Send emails as said user. 

In 99% of scenarios, this requirement from an application is not required unless the application was created for a very custom workflow, in which admins should still be configured to grant the consent manually (or, again, through a specific workflow).

As an administrator, you should limit what permissions users can self-grant applications access to. This should still be carefully reviewed and aligned with the application’s requirements. While that is covered in separate posts of mine, in this post, I will show you how you can generate reports of user-consented permissions to applications in your tenant and then export this to a CSV.

Requirements

This tutorial utilises the Microsoft Graph PowerShell SDK. If you don’t have it installed, or it has been a while since you installed it, check out my post to guide you through installing and updating the modules. How to install the Microsoft Graph PowerShell SDK.

You will also need access to a Global Administrator account in your tenant. This is so you can grant the initiate tenant-wide permissions to the Microsoft Graph Command Line Tools application.

The user-consented permissions report script

You can copy the below script and modify it to your liking, or you can run it how it is and generate a report. 

The script utilises batching requests to improve the performance of checking permissions on almost 500 service principals in Microsoft Entra. It will then filter out any permission grants that are consented to at the user level. 

As this user grant information does not contain the user’s username or application name, the script will loop through each object ID to obtain the meaningful name and add them to a report named $final. 

You will then be prompted to choose where the report will be exported and saved.

Find this script on my public GitHub repository.

<#
.SYNOPSIS
Generates a report of user-consented permissions to application in Microsoft Entra
.LINK
   https://ourcloudnetwork.com
   https://www.linkedin.com/in/danielbradley2/
.NOTES
   Version:        0.1
   Author:         Daniel Bradley
   Creation Date:  Monday, January 8th 2024, 6:54:33 am
   File: ApplicationUserConsentReport.ps1
   Copyright (c) 2024 Your Company

HISTORY:
2024-01-08	File Generated

.INPUTS
.OUTPUTS

.COMPONENT
 Required Modules: 
 Microsoft.Graph.Applications
 Microsoft.Graph.Users
 
.EXAMPLE
.\ApplicationUserConsentReport.ps1

.LICENSE
Use this code free of charge at your own risk.
Never deploy code into production if you do not know what it does.
#>

#Connect to graph
Connect-MgGraph -Scopes Application.Read.All, user.read.all

#Define output file name
$filename = "User_Permissions_Grant_Report.csv"

#Function will launch Windows explorer to select a folder
Function SelectFolder {
    $global:folderselection = New-Object System.Windows.Forms.OpenFileDialog -Property @{  
    InitialDirectory = [Environment]::GetFolderPath('Desktop')  
    CheckFileExists = 0  
    ValidateNames = 0  
    FileName = $filename  
    }
    $folderselection.ShowDialog() | Out-Null
}

#Gets all service principals with limited info
Write-host "Gathering service principals.." -ForegroundColor Yellow
$AllServicePrincipals = Get-MgServicePrincipal -All | Select Id, appid, DisplayName

#Send batch requests to gather all user grants, due to large number of service principals
Write-host "Indentifying user grants..." -ForegroundColor Yellow
$Report = @()
for($i=0;$i -lt $AllServicePrincipals.count;$i+=20){
    $batch = @{}
    $batch['requests'] = ($AllServicePrincipals[$i..($i+19)] | select @{n='id';e={$_.id}},@{n='method';e={'GET'}},`
		@{n='url';e={"/servicePrincipals/$($_.id)/oauth2PermissionGrants"}})
    $response = invoke-mggraphrequest -Method POST -URI "https://graph.microsoft.com/v1.0/`$batch" -body ($batch | convertto-json) -OutputType PSObject
    $Report += $response.responses
}
$UserGrants = $Report.body.value | Where {$_.ConsentType -eq "Principal"}

#Uses loop function to gather additional information such as the userprincipalname. It also correlates the service principal name.
Write-host "correlating information..." -ForegroundColor Yellow
$final = [System.Collections.Generic.List[Object]]::new()
Foreach ($Grant in $UserGrants){
    $user = $null
    $app = $null
    $user = Get-MgUser -UserId $Grant.principalId | Select UserPrincipalName
    $app = $AllServicePrincipals | Where {$_.id -eq $($grant.clientid)}
    $obj = [PSCustomObject][ordered]@{
        "Application" = $app.DisplayName
        "user" = $user.UserPrincipalName
        "permission" = ($grant.scope.Trim() -replace " ",", ")
    }
    $final.Add($obj)
}

#Run select folder function
Write-host "Please select a folder `n" -ForegroundColor Green
Start-Sleep -Seconds 2
SelectFolder

#Export file
$final | Export-CSV  -Path $folderselection.FileName -NoTypeInformation
Write-host "File saved! Click Enter to close... `n" -ForegroundColor Green
Write-host "        Script by  >>  Daniel Bradley   " -ForegroundColor cyan -BackgroundColor black
Write-host "            Visit  >>  ourcloudnetwork.com  " -ForegroundColor cyan -BackgroundColor black
Write-host "          Connect  >>  https://www.linkedin.com/in/danielbradley2/  " -ForegroundColor cyan -BackgroundColor black
Write-host "`n"
Pause

Once the script has run, you will receive a report like the below.

User consent report
User consent report

Wrapping up

It is important to take back control of applications in your environment. Even after you have prevented users from creating applications in your tenant and restricted application user consent, there are likely some permissions lurking on the applications, which this script will help you find!

Daniel Bradley

My name is Daniel Bradley and I work with Microsoft 365 and Azure as an Engineer and Consultant. I enjoy writing technical content for you and engaging with the community. All opinions are my own.

Leave a Reply