🚀 Executive Summary
TL;DR: Excessive MS Graph permissions in PowerShell scripts create significant security vulnerabilities by violating the Principle of Least Privilege. This guide provides three distinct methods to accurately identify and apply only the necessary permissions, moving from risky over-provisioning to secure, minimal-privilege automation.
🎯 Key Takeaways
- Utilize `Find-MgGraphCommand` within the Microsoft Graph PowerShell module to quickly identify the least permissive API permissions required for specific cmdlets when building new scripts.
- For complex or inherited scripts, employ a network sniffer (e.g., Fiddler) to trace actual Graph API calls made with delegated permissions, providing irrefutable evidence for mapping to minimum required permissions.
- Leverage the Azure AD App Registration’s API permissions blade to audit and identify permissions that haven’t been used recently, serving as a starting point for investigating and purging excessive access.
Tired of granting god-mode permissions to your PowerShell scripts? Learn how to pinpoint the exact, minimal MS Graph permissions you need, moving from risky over-provisioning to secure, least-privilege automation.
How to Identify Excessive MS Graph Permissions for Your PowerShell Scripts (Without Losing Your Mind)
I still get a cold sweat thinking about it. It was 3 AM, and my phone was buzzing off the nightstand. A junior engineer had been tasked with a “simple” script: pull a list of licensed users for an audit. He’d used a service principal we had lying around for general automation. What he didn’t know was that this service principal had been granted Directory.ReadWrite.All months ago by someone in a hurry. His script had a logic bug—an unterminated loop—that started trying to modify user objects. We caught it fast, but for ten terrifying minutes, our production Azure AD was getting hammered with bogus update requests. All because of a permission that was never needed in the first place. That’s why we’re here. Let’s stop guessing and start engineering.
The “Why”: The Deadly Sin of Convenience
Let’s be honest. The root of this problem is pressure. You’ve got a ticket that needs to be closed, the script needs to run on automation-svc-prod-01 by end of day, and you get that dreaded “Authorization_RequestDenied” error. What do you do? You go back to the Azure AD App Registration, search for a permission that sounds right, and add something broad like User.ReadWrite.All because it works and you can move on. We’ve all done it.
The issue is that the Microsoft Graph SDK for PowerShell abstracts away the underlying API calls. You run Get-MgUser, and it’s not immediately obvious if that requires User.Read.All, Directory.Read.All, or something else entirely. This abstraction, meant to make our lives easier, often leads us to violate the Principle of Least Privilege, the golden rule of security. We give our scripts the keys to the entire kingdom when they just need to peek in one room.
Solution 1: The Quick Fix (The Command-Line Detective)
This is my go-to when I’m building a new script and want a quick, accurate answer without leaving my console. The Microsoft Graph team gave us a fantastic tool right inside the module: Find-MgGraphCommand.
Let’s say you know you need to get user information. You can use this cmdlet to discover the command and, more importantly, see the permissions it requires.
# Find commands related to getting users
Find-MgGraphCommand -Command Get-MgUser
# The output is gold. It gives you the command, the module, and the permissions.
# It will look something like this:
Command Module Permissions
------- ------ -----------
Get-MgUser Microsoft.Gr... {Directory.Read.All, Directory.ReadWrite.All, User.Read.All, User.ReadWrite.All}
Look at that `Permissions` column. It gives you a list of API permissions that will satisfy the command. Your job is to pick the least permissive one from that list. In this case, User.Read.All is clearly a better choice than Directory.ReadWrite.All. This method is reactive and perfect for building net-new scripts one command at a time.
Darian’s Pro Tip: Be ruthless here. If the command lists both a `Read.All` and a `ReadWrite.All` permission, and your script is only reading data, never choose the `ReadWrite` option. Document why you chose the lesser permission in your commit message. Your future self (and your security team) will thank you.
Solution 2: The Permanent Fix (The Network Sniffer)
Okay, the command-line tool is great, but what about a complex script with dozens of calls? Or what if you’ve inherited a monster script and have no idea what it’s really doing? It’s time to trace the actual API calls.
This is more involved, but it’s the most accurate method. The plan is to run your script using delegated permissions (i.e., running as your own user account) and watch the network traffic to see exactly which Graph API endpoints it’s hitting.
- Connect with Delegated Permissions: Instead of using a certificate or secret, connect using your own interactive login.
Connect-MgGraph -Scopes "User.Read.All,Group.Read.All" # etc. - Fire up a Web Debugger: Use a tool like Fiddler, Charles Proxy, or even your browser’s built-in Developer Tools (F12). Start capturing traffic.
- Run Your Script: Execute the PowerShell script from start to finish, making sure it hits all the code paths.
- Analyze the Traffic: In your debugger, filter for requests to
graph.microsoft.com. You will see the raw REST API calls your script is making, likeGET /v1.0/usersorPATCH /v1.0/groups/{group-id}. - Map Calls to Permissions: Now, take each of those API calls and look them up in the official Microsoft Graph permissions documentation. The docs will tell you the absolute minimum Application and Delegated permissions required for that specific call.
This is, admittedly, a bit of a pain. But it provides irrefutable evidence of what your script actually needs. It’s how you take a bloated service principal with 15 permissions and pare it down to the 3 it truly requires.
Solution 3: The ‘Nuclear’ Option (The Audit & Purge)
This isn’t for building a new script. This is for when you’ve taken over an environment and you’re staring at an App Registration named “Legacy-Automation-Do-Not-Touch” that has 42 API permissions, half of which are for services we don’t even use anymore.
Azure AD has a built-in feature to help with this. It’s not perfect, but it’s a powerful starting point.
- Navigate to your Azure AD App Registration.
- Go to the API permissions blade.
- At the top, look for a column or banner (the UI changes) that mentions reviewing permissions. You might see a “Review permissions for [App Name]” link or a recommendation.
- Azure will sometimes show you which permissions it has not seen used in the last 30/60/90 days.
This is your hit list. If a powerful permission like Mail.ReadWrite.All hasn’t been used in 90 days, it’s a prime candidate for removal. You are essentially using Microsoft’s own telemetry to find the cruft.
Warning: This is a powerful tool, but use it with caution. A permission might be used for a quarterly report script that just hasn’t run in the last 30 days. Don’t just blindly remove everything. Use this as a starting point for an investigation, not a one-click fix. Correlate the unused permission with the scripts that use the service principal before you hit the “Remove” button.
Ultimately, managing Graph permissions is about discipline. It’s about resisting the urge to take the easy path and instead taking the secure one. Start with the right tools, document your choices, and treat your service principals like the privileged accounts they are.
🤖 Frequently Asked Questions
âť“ How can I ensure my PowerShell scripts only use the necessary MS Graph permissions?
You can use `Find-MgGraphCommand` for new scripts to identify minimal permissions per cmdlet, employ a network sniffer like Fiddler to trace actual API calls for complex scripts, or leverage Azure AD’s API permissions audit feature to review and remove unused permissions.
âť“ What are the different approaches to identifying MS Graph permissions, and when should I use each?
`Find-MgGraphCommand` is ideal for building new scripts command-by-command. A network sniffer (e.g., Fiddler) is best for complex or inherited scripts to trace actual API calls. The Azure AD App Registration’s audit feature is for reviewing and purging unused permissions in existing, potentially over-provisioned service principals.
âť“ What is a common implementation pitfall when managing MS Graph permissions, and how can it be avoided?
A common pitfall is granting overly broad permissions (e.g., `User.ReadWrite.All`) out of convenience, violating the Principle of Least Privilege. This can be avoided by rigorously selecting the least permissive option (e.g., `User.Read.All` instead of `User.ReadWrite.All` if only reading) and documenting the rationale.
Leave a Reply