Table of Contents
Extract and Report Delegated Permission Assignments with the Microsoft Graph PowerShell SDK
When discussing permissions used to retrieve data with Graph API requests (including cmdlets from the Microsoft Graph PowerShell SDK), most of the time we refer to application permissions rather than delegate permissions. The reason is simple: when automating operations with PowerShell, tenant administrators usually process data drawn from multiple sources, like all user mailboxes or all groups. This level of processing requires application permissions.
Delegated permissions (also called scopes) allow apps to access information on behalf of the signed in user. Anything that user can access, the app can too. Usually, the user is the owner of a resource (like their mailbox), but they can gain access to information through an RBAC role, such as Teams administrator.
Delegated permissions are granted by a specific resource (like a Graph API) and represent the operations that an app can perform for the user. For instance, the Mail.Read scope allows an app to read messages in the user’s mailbox. The grant of consent for a delegated permission usually happens when a user signs into an app and the app discovers that consent for the required permission is not granted. At this point, Entra ID displays the consent prompt window to allow the user to give consent for the app to use the permission and proceed.
Reporting Permissions
Application permissions assigned to apps can be checked by examining the app role assignments for service principals. It’s a good idea to inventory app permissions periodically to ensure that apps don’t have high-profile permissions without good reason.
To report delegated permissions, we need to check delegated permission grants (otherwise called OAuth2 permission grants). These are delegated permissions granted for a client application to access an API on behalf of a signed-in user. The Microsoft Graph PowerShell SDK cmdlet used for this purpose is Get-MgOauth2PermissionGrant. The Directory.Read.All permission is required to read details of delegated permissions and user accounts.
Interpreting a Delegated Permission for Users
After connecting, run the Get-MgUser cmdlet to create an array of user accounts to query. Usually, I apply a filter to find licensed accounts. Once you have a set of accounts, it’s a matter of looping through the set to find the delegated permissions for each account:
[array]$Permissions = Get-MgUserOauth2PermissionGrant -UserId $User.Id -All
An individual permission assignment looks like this:
$Permission | Format-List ClientId : 5482d706-b547-4b9d-b159-b91a5776e0e9 ConsentType : Principal Id : BteCVEe1nUuxWbkaV3bg6YnEoxRs7QVAltG-nFdw96NYzfTvuBuZSJTeeV9la0oY PrincipalId : eff4cd58-1bb8-4899-94de-795f656b4a18 ResourceId : 14a3c489-ed6c-4005-96d1-be9c5770f7a3 Scope : openid profile User.ReadWrite User.ReadBasic.All Sites.ReadWrite.All Contacts.ReadWrite People.Read Notes.ReadWrite.All Tasks.ReadWrite Mail.ReadWrite Files.ReadWrite.All Calendars.ReadWrite Group.Read.All Group.ReadWrite.All Directory.AccessAsUser.All Directory.ReadWrite.All User.ReadWrite.All IdentityRiskEvent.Read.All Reports.Read.All AuditLog.Read.All User.Read SecurityEvents.ReadWrite.All offline_access TeamSettings.Read.All TeamSettings.ReadWrite.All Mail.ReadBasic Chat.Read Chat.ReadBasic Analytics.Read AdditionalProperties : {}
- The client identifier points to the service principal for the client app. In this case, it is the Graph Explorer.
- The principal identifier points to the identifier for the user account. Because we’re listing delegated permissions by user, the consent type for the permission is always Principal, meaning that the app is limited to impersonating the specific user. If the consent type is AllPrincipals, meaning that the app can use the consent to impersonate all users, the principal identifier would be empty.
- The resource identifier points to the service principal for the resource. In this example, the resource identifier points to “Microsoft Graph” (the Graph API). The set of permissions (Scope) confirm this because they are Graph permissions. As you can see, the Graph Explorer has consent for many permissions. This is a normal situation if developers use the Graph Explorer to test different Graph APIs.
Processing Delegated Permissions for AllPrincipals
After processing the delegated permission assignments for user accounts, we process those for all principals (any user). The set of assignments is found with:
[array]$AppGrants = Get-MgOauth2PermissionGrant -filter "consentType eq 'AllPrincipals'" -All
Steps in the Script
The steps in the script are as follows:
- Find the set of user accounts.
- For each account, check if any delegated permissions exist.
- For each permission, check the client app and resource.
- Find the set of delegated permissions for all principals.
- Do much the same as for individual assignments.
- Report what’s been found.
Figure 1 shows the output generated.
You can download the full script from GitHub.
Interpreting the Results
It’s inevitable that delegated permissions will accumulate over time. Looking at the results from my tenant, I see evidence of the iOS account migration to modern authentication from 2021, apps from conference organizers like Sessionize and Community Days, the app used to register for the Microsoft Technical Community, and so on. All these assignments are understandable. The question is whether the assignments are needed any longer and if not, should they be removed. That’s up to you…
Support the work of the Office 365 for IT Pros team by subscribing to the Office 365 for IT Pros eBook. Your support pays for the time we need to track, analyze, and document the changing world of Microsoft 365 and Office 365.
Do you know what permissions are required to find out the delegated permissions?
I do. And the article says: “The Directory.Read.All permission is required to read details of delegated permissions and user accounts.”
https://learn.microsoft.com/en-us/graph/api/oauth2permissiongrant-get?view=graph-rest-1.0&tabs=http