The delegated OAuth grant that outlives the employee
Disabling an Entra account does not delete the delegated OAuth grants the person consented to. A third-party app can keep acting on a former employee until the grant itself is revoked. Here is how to read them and which scopes to treat as high risk.
An account can be disabled while the apps that account consented to keep their standing access. A delegated OAuth grant is a permission the user gave a third-party app to act on their behalf, and it lives on the grant object, not on the sign-in state. So a former employee can be gone from every login screen while an app they once approved still holds Mail.Send or Files.ReadWrite.All against the tenant. This note covers how the ITSailor scan reads that and how it scores the scopes.
Last verified: 2026-08-03.
Test conditions
The scan reads oauth2PermissionGrants and joins them against disabled users and against the service-principal inventory. Two questions get answered that a licence report never can: which former users still hold delegated grants, and which third-party apps hold high-risk scopes regardless of who consented.
Results
The tool raises a critical finding when a disabled user still owns delegated grants. Separately it raises a high finding for third-party apps holding scopes on this pattern list: any scope containing ReadWrite, Mail.Send, Files.Read.All, Files.ReadWrite.All, Directory.ReadWrite.All, or offline_access. The last one, offline_access, is the quiet one: it is the refresh-token scope that lets an app keep working without the user present.
Scope risk is graded, not binary. Write, send, or admin-grade access to mail, files, or the directory reads as high. Broad read across the organisation reads as medium. A grant consented for all users (admin consent) outranks a single per-user grant, because its reach is the whole tenant.
# List delegated grants and flag high-risk scopes
Connect-MgGraph -Scopes "Directory.Read.All"
Get-MgOauth2PermissionGrant -All |
Where-Object { $_.Scope -match 'ReadWrite|Mail\.Send|Files\.Read|Directory\.ReadWrite|offline_access' } |
Select-Object ClientId, ConsentType, PrincipalId, Scope
Failures and edge cases
Microsoft first-party apps are excluded so the list stays about third-party reach, matched by owner tenant, known app id, or a publisher that starts with Microsoft. A grant that points at a service principal the scan did not fetch is skipped rather than guessed at. An unverified publisher is worth a second look even at a medium scope.
Where this does not apply and limitations
This reads Entra delegated grants for one tenant. It does not cover application permissions granted to app registrations without a user principal, and it does not judge whether a given app is safe, only what it can reach. Revoking a grant can break a live integration, so read the app before you revoke. The full SaaS and shadow-IT view is at /tools/saas-auditor.
Offboarding Evidence carries this same delegated-grant read into the per-departure evidence pack, so a grant that outlives the employee is recorded against that departure rather than left for the next SaaS audit, from Offboarding Evidence.
Sources and further reading
Test the same boundary in your environment.
Use a focused diagnostic to compare the lab result with the controls and constraints in your own environment.
Choose a diagnosticMore from Ops Log
The Google Workspace scope for reading a leaver's app grants is not read-only
Listing a Google Workspace user's third-party app tokens needs admin.directory.user.security, the same scope that deletes them, and Google lists no read-only variant. Microsoft Graph lists the equivalent grants with Directory.Read.All. Treat the Google credential as a revocation key.
Google's admin.directory.user.security scope has no read-only form
Google publishes no read-only form of the admin.directory.user.security scope. The scope that lists a user's OAuth tokens and app passwords is the same scope that deletes them, so treat any grant of it as a write permission when reviewing a third-party app.
A daily offboarding check can prove closure only as a bound
A leaver check that runs once a day can report closure only as a bound between two runs. A Graph 429, including one inside a batch that returns 200, and a membership read that returns nulls can each make a degraded run look clean, so an absence counts only from a run that read cleanly.