Security

Why Microsoft Graph API Permissions Are Your Biggest Security Blind Spot

GraphWarden Team·April 10, 2025·8 min read

The Permission Paradox

When an application needs to read user profiles from your Microsoft 365 tenant, it requests the User.Read.All permission. This single permission grants access to every user object in the directory - all 500, 5,000, or 50,000 of them - with every property exposed by the Graph API.

There is no User.Read.HRDepartment or User.Read.SalesTeamOnly. Microsoft Graph permissions are horizontal: they span the entire tenant. If your HR application needs to read 47 employees in the HR department, it still gets access to every user in the organization. This is not a bug. It is by design.

The consequence is stark. Every application with User.Read.All can enumerate your entire workforce, including contractors, executives, and service accounts. The permission model offers no way to scope access to a subset of users, a specific department, or a particular set of properties.

What Can Go Wrong

The gap between what an app needs and what it can access creates several concrete risks.

  • Data leakage through over-exposure. An app that only needs displayName and mail also receives mobilePhone, passwordProfile, onPremisesExtensionAttributes, and dozens more properties. Any of these could contain personally identifiable information that privacy regulations require you to protect.
  • Over-privileged third-party integrations. When you connect a CRM, ServiceNow, or any SaaS tool to Graph, they get access to the same all-or-nothing permission set. If that vendor is breached, the attacker has a ready-made directory of your entire organization.
  • Compliance violations. GDPR, HIPAA, SOC 2, and Quebec's Law 25 all require data minimization - applications should only access the data they need to function. An app with blanket tenant access is the opposite of minimization, and auditors increasingly ask about it.
  • Lateral movement risk. A compromised app credential does not just expose the data the app was designed to use. It exposes everything the permission allows. With User.Read.All, that means the full directory - a treasure trove for social engineering, privilege escalation, and targeted attacks.

Why Microsoft Cannot Solve This at the Platform Level

This is not negligence on Microsoft's part. The Graph API permission model is designed for platform-level access control: which application can reach which resource type. It answers the question "Can this app read users?" but it was never built to answer "Which users can this app read?" or "Which properties should be returned?"

Adding granular, per-application scoping to Graph permissions would be an enormous platform change. Microsoft would need to support custom scopes, per-object ACLs, and property-level policies across hundreds of API endpoints. Even with Entra ID's conditional access and app consent policies, none of these address the fundamental issue: once an app has a Graph permission, it has it for the entire tenant.

Some organizations try to mitigate this with multiple app registrations, each with different permissions, but this creates an administrative nightmare. Others attempt to build custom middleware, but maintaining a secure, performant proxy layer is a full-time engineering effort.

The Cost of Inaction

The cost of ignoring this gap is not theoretical. Consider the following scenarios:

  • A third-party HR tool is compromised. The attacker uses its User.Read.All token to enumerate every employee, including C-suite mobile numbers and home addresses stored in extension attributes. The resulting data breach triggers GDPR notification obligations in multiple jurisdictions.
  • An internal audit reveals that 14 applications have User.ReadWrite.All, but only 2 actually need write access. The others accumulated permissions over time and nobody removed them. Three of those apps have client secrets embedded in PowerShell scripts stored in shared drives.
  • A legacy PowerShell script with hardcoded credentials has been running unmonitored for three years. It reads the full user directory every night. Nobody knows what it does with the data, who wrote it, or whether the credentials have been shared.

Regulatory fines for data breaches involving personal information range from hundreds of thousands to millions of dollars. Under GDPR, fines can reach 4% of global annual revenue. Under Quebec's Law 25, penalties can reach $25 million CAD. These are not edge cases - they are the predictable outcome of ungoverned API access.

A Better Approach: The Proxy and Policy Model

If the platform cannot provide granular Graph API governance, the solution must live outside the platform - as an intelligent intermediary between your applications and Graph.

The idea is straightforward: instead of giving each application direct access to Microsoft Graph with broad permissions, route all Graph traffic through a policy-aware proxy. The proxy holds the real Graph credentials (securely, in Azure Key Vault), and applications authenticate with separate, revocable proxy credentials. A YAML-based rule engine defines, per-application:

  • Which HTTP methods are allowed (read-only vs. read-write)
  • Which endpoints can be called
  • Which objects are returned (filtered by AAD group membership)
  • Which properties are included in the response
  • Which transforms are applied to sensitive data (masking, redaction, hashing)

This is the model that GraphWarden implements. It deploys as a lightweight Windows service in your environment, intercepts all Graph API calls, and enforces your policies before any data reaches the consuming application. Every call is logged with full context: which app, which endpoint, which rule matched, how many objects were filtered, and how long it took.

The key advantage is that your applications change almost nothing. A PowerShell script updates two lines: the token endpoint and the base URL. A .NET application swaps the MSAL token acquisition for a simple HTTP POST to the proxy. No SDK changes, no architectural overhaul.

Closing the Gap

Microsoft Graph API permissions are a powerful enabler, but they are a blunt instrument for access control. The gap between what an app is authorized to access and what it needs to access is your biggest security blind spot - and it grows with every new application, every new integration, and every legacy script that nobody dares to touch.

Closing that gap requires a policy layer that sits between your applications and Graph: one that enforces data minimization, manages credentials securely, and provides the audit trail that compliance demands.

If your organization runs Microsoft 365 and has more than a handful of Graph-integrated applications, this is a problem worth solving today - not after the next audit finding or the next breach.

Request a Demo