Exchange Online Application Access Policies: Constraining Graph Application Permissions to Specific Mailboxes
Application permissions grant tenant-wide access by default. Exchange Online Application Access Policies let you scope Graph Mail.Read to a mail-enabled security group, enforcing least privilege for daemon services.
22 Nov 2025, 04:30 UTC

Granting an Azure AD application the Mail.Read permission in Microsoft Graph creates a service principal that can read every mailbox in the tenant. For automated extraction jobs running without user interaction, that default violates least privilege and turns a compromised secret into a tenant-wide data exposure. Exchange Online Application Access Policies provide the supported control to narrow that blast radius to a defined set of mailboxes.
Requirements for Scoped Extraction
An unattended service needs three things: an identity that does not depend on a user session, a narrowly scoped permission grant, and a mailbox boundary enforced by Exchange Online rather than by application code. The service must run under application permissions—delegated permissions require a signed-in user and break daemon scenarios. The permission grant should be read-only unless the job writes mail. Finally, the mailbox set must be maintained by tenant administrators, not embedded in the application.
Smallest Suitable Design
The minimal architecture has four components:
- An Azure AD app registration representing the extraction service.
- A client credential—preferably a certificate stored in Azure Key Vault—authenticated through the client credentials flow.
- A mail-enabled security group whose members are the only mailboxes the service may access.
- An Exchange Online Application Access Policy binding the app's service principal to that group.
The app registration receives the Mail.Read application permission with tenant-admin consent. The service principal object ID from Azure AD becomes the policy target. The mail-enabled security group acts as the scope boundary; regular security groups are not sufficient because Exchange Online requires a mail-enabled group to resolve the policy scope.
Trust and Data Boundaries
Two trust boundaries are in play. The first is Azure AD: the tenant administrator consents to the application permission, which by default authorizes the service principal against all mailboxes. The second is Exchange Online: the Application Access Policy overlays a restriction so that even though the token carries the Mail.Read role, Exchange Online rejects requests for mailboxes outside the policy group.
The data boundary is therefore enforced at the resource server, not in the token. A token issued for the service principal looks identical whether or not a policy exists; the difference appears only when Graph forwards the request to Exchange Online. This means the policy is a server-side control that survives token caching and does not require application changes.
Configuring the Policy
Run the following in Exchange Online PowerShell using the ExchangeOnlineManagement module. You need Exchange Administrator or Global Administrator rights, and the app must already have admin-consented Mail.Read.
# Placeholders:
# <service-principal-object-id> : Object ID from Azure AD enterprise applications
# <group-email> : Mail-enabled security group email address
New-ApplicationAccessPolicy `
-AppId "<service-principal-object-id>" `
-PolicyScopeGroupId "<group-email>" `
-AccessRight RestrictAccess `
-Description "Limit extraction service to approved mailboxes"
Verify parameter names against the current module version before running in production. The RestrictAccess right means the app can access only mailboxes in the group. The alternative, DenyAccess, blocks the app from the group while allowing it elsewhere.
Operational Checks
Confirm the policy is in place and the group membership is correct:
Get-ApplicationAccessPolicy | Format-List AppId,PolicyScopeGroupId,AccessRight
Get-DistributionGroupMember -Identity "<group-email>" | Select-Object PrimarySmtpAddress
Test with an app-only token obtained through the client credentials flow. A request to an allowed mailbox should return HTTP 200 with message data:
GET https://graph.microsoft.com/v1.0/users/{allowed-mailbox}/messages
A request to a mailbox outside the group should return HTTP 403 with an access-denied error. If the outside request succeeds, the policy has not propagated or the group is not mail-enabled.
Policy changes are not instantaneous. Allow time for Exchange Online replication before treating a failed test as a configuration error. Monitor Azure AD sign-in logs for the service principal to detect unexpected client IP addresses or token issuance spikes.
Failure Modes
- Group type mismatch: Using a standard security group instead of a mail-enabled security group causes the policy to fail silently or to not scope as expected.
- Replication delay: A newly created policy may not apply immediately, producing intermittent access-denied responses for allowed mailboxes.
- Credential expiry: A missed certificate or secret rotation results in
invalid_clienterrors from the token endpoint. - Permission drift: If an administrator revokes the
Mail.Readgrant, the token no longer contains the required role claim and Graph returns 403. - Scope creep: Adding a mailbox to the group grants access immediately once replication completes; conversely, removing a mailbox revokes access, but cached tokens may remain valid until expiry.
Conditions That Change the Design
If the extraction job must act on behalf of a signed-in user, switch to delegated permissions and drop the Application Access Policy—it has no effect on delegated tokens. If the service needs SharePoint or OneDrive data, this policy does not constrain those workloads; use separate app registrations or administrative units for those resource types. If every mailbox in the tenant genuinely requires access, remove the policy and compensate with certificate-based authentication, Key Vault secret management, and strict sign-in monitoring. Finally, if the requirement flips from "allow only this group" to "block this group," change AccessRight to DenyAccess rather than redesigning the app.
Application Access Policies do not retroactively restrict tokens already issued; they filter requests at the Exchange Online layer. Treat them as a server-side guardrail that reduces the impact of credential compromise, not as a replacement for secure credential storage.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.