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.
Listing the third-party applications a Google Workspace user has authorised, the check an offboarding review runs against a leaver, needs an OAuth scope that can also delete those grants, delete the user's application-specific passwords and invalidate their backup verification codes. Google's OAuth scopes page lists no read-only variant of that scope. Microsoft Graph lists the equivalent delegated permission grants with a read permission.
Last verified: 2026-09-15.
What Google documents
The Admin SDK Directory API exposes a user's granted applications through the tokens resource, read 2026-09-15. It describes the list method in one line: it "Returns the set of tokens specified user has issued to 3rd party applications." Each token carries a clientId, a scopes array, a displayText name for the application, and two booleans, anonymous and nativeApp. The tokens.list reference, read the same day, names one required authorisation scope: https://www.googleapis.com/auth/admin.directory.user.security. The tokens.delete reference names the identical scope for the operation that "Deletes all access tokens issued by a user for an application."
The OAuth 2.0 Scopes for Google APIs page, read 2026-09-15, describes that scope in one line: "Manage data access permissions for users on your domain." The row directly above it, for ordinary profile data, reads differently: admin.directory.user.readonly, "See info about users on your domain." A read-only scope exists for basic user fields. The page lists no admin.directory.user.security.readonly, and the tokens.list reference offers no alternative scope for the call.
The scope that lists also deletes, across three resources
The same arrangement holds beyond tokens. The asps.list method, which "Lists the ASPs issued by a user", meaning application-specific passwords, requires admin.directory.user.security, and so does asps.delete, which "Deletes an ASP issued by a user." The verificationCodes.list method, which "Returns the current set of valid backup verification codes for the specified user", requires the same scope, as do verificationCodes.invalidate and the generate method beside it. Each of those method references, read 2026-09-15, names that one scope and no other. One scope therefore gates reading and changing three kinds of standing access: OAuth grants, application passwords and backup codes.
| Object read | Microsoft Graph, least privileged | Google Workspace, required scope | Read-only option documented |
|---|---|---|---|
| Delegated app grants | Directory.Read.All | admin.directory.user.security | Graph yes, Workspace no |
| Application-specific passwords | not evaluated here | admin.directory.user.security | Workspace no |
| Backup verification codes | not evaluated here | admin.directory.user.security | Workspace no |
| Grant and revoke events, history only | not evaluated here | admin.reports.audit.readonly | Workspace yes |
Microsoft Graph separates reading from changing for the object it shares with Google. The oAuth2PermissionGrant resource, read 2026-09-15, "Represents the delegated permissions that have been granted to an application's service principal." The List oauth2PermissionGrants reference, read the same day, gives Directory.Read.All as the least privileged permission for both delegated and application access, and names DelegatedPermissionGrant.ReadWrite.All and Directory.ReadWrite.All as the higher privileged permissions. The Microsoft Graph permissions reference, also read 2026-09-15, describes Directory.Read.All as allowing the app "to read data in your organization's directory, such as users, groups and apps." A credential that lists a tenant's delegated grants can therefore hold a permission whose documented description is limited to reading.
Why it is easy to get wrong
An administrator who has built a Microsoft offboarding check reaches for the same shape on the Google side: a read scope for the audit script, a separate write scope held back for remediation. Google's naming invites that assumption. Of the 26 Admin SDK Directory scopes on the OAuth 2.0 Scopes for Google APIs page, read 2026-09-15, 14 carry no .readonly suffix, and 12 of those 14 have a matching .readonly variant on the same page; the two without one are admin.directory.device.mobile.action and admin.directory.user.security. An administrator who has used admin.directory.user.readonly for a directory export has every reason to expect the pattern to hold here. The scope name gives no hint that it is unpaired, and the scopes page gives each row a single line of description with no note on the missing variant.
What it costs
A reporting pipeline built to list the connected-app grants a leaver still holds, following the practice of requesting the narrowest scope that satisfies the call, ends up holding admin.directory.user.security whatever its intent, because the tokens.list reference names no other scope to ask for. That credential, whether a delegated administrator session or a service account acting through domain-wide delegation, can also call tokens.delete, asps.delete and verificationCodes.invalidate. The tokens.delete and asps.delete references take the target account as a userKey path parameter that accepts "the user's primary email address, alias email address, or unique user ID", so nothing in the scope or the request shape confines the credential to the one account under review. A key stored for a recurring read-only report is, on the wire, a key that can revoke application access and invalidate backup codes on accounts other than the one being reviewed, the moment it is copied, logged or misused.
Google does publish a read-only path to part of the same information, and it answers a narrower question. The Reports API activities.list method, read 2026-09-15, requires only https://www.googleapis.com/auth/admin.reports.audit.readonly, and with applicationName set to token it returns the events listed on the OAuth Token Audit Activity Events page, read the same day: authorize, revoke, request, deny and activity. That is a history of grants and revocations rather than the set of tokens a user holds now, and the events page states no retention period, so a grant made before the retained history begins would not appear. A review that must state which grants are live today still needs tokens.list and its scope. A review that only needs the authorisations inside a known window can run on the read-only audit scope.
flowchart TD
accTitle: Permission needed to list a leaver's app grants, per platform
accDescr: To list a leaver's app grants, Microsoft Graph needs only the read permission Directory.Read.All, while Google Workspace needs admin.directory.user.security, which also deletes tokens and application passwords, so the Graph credential can be monitored and the Workspace credential is acted on now and kept short-lived.
A[List leaver app grants] --> B{Which platform?}
B -->|Entra| C[Directory.Read.All]
B -->|Workspace| D[Workspace security scope]
C --> E[Read only]
D --> F[Also deletes tokens]
E --> H[Monitor only]
F --> G[Act now, short-lived key]Prerequisites
A Google Workspace super administrator account, or a service account with domain-wide delegation acting for one, holding an OAuth token authorised for https://www.googleapis.com/auth/admin.directory.user.security. The userKey of the account under review: the tokens.list reference accepts the primary email address, an alias email address or the unique user ID. The check below issues no write call. Record the scope grant itself before running it, because the grant is where the side effect lives.
# Read-only. Lists the third-party app tokens one user has issued.
# Requires https://www.googleapis.com/auth/admin.directory.user.security,
# which is also the scope that can delete every token this call returns.
GET https://admin.googleapis.com/admin/directory/v1/users/{userKey}/tokens
Authorization: Bearer {token authorised for admin.directory.user.security}
Expected output
The tokens.list reference documents a successful response as an object of kind admin#directory#tokenList carrying an etag and an items array of Token resources. Each item is one application the reviewed user has authorised: its clientId, its displayText name, the scopes it was granted, and two booleans. The tokens resource reference sets nativeApp to true "if the application is installed to a desktop or mobile device" and anonymous to true "if the application has an anonymous Client ID". The reference does not show the response for a user with no grants, so record exactly what comes back, an empty or absent items array included, as the result of the review.
Side effects
The call itself changes nothing: tokens.list is a GET. The side effect sits one layer up, in the authorisation that made the call possible. Whatever principal is granted admin.directory.user.security to run this check gains a standing ability to call the delete and invalidate methods described above, whether or not the review ever uses them. Enter that grant in the same access log as any delete-capable credential, because it is one.
Rollback
The call leaves nothing to roll back. The credential is what needs undoing. If a service account or a delegated administrator session was authorised for admin.directory.user.security only to run this read, remove the scope from that credential once the review is complete, or replace a standing grant with a short-lived token requested per run. No narrower scope serves tokens.list, so where the review needs current state, the controls left are how long the credential exists and who can invoke it. Where event history is enough, the read-only audit scope described above can replace it.
Limitations
This note reports what the vendor references document about the scopes and permissions for the equivalent objects. It does not test what either platform does to a grant when an account is suspended or deleted. Google's page on suspending a user, read 2026-09-15, lists what is blocked (new email and calendar invitations, and Google Workspace services such as Drive and Gmail) and what persists (email, documents, calendars and other data, and collaborators' access to shared documents), and names no OAuth token, third-party app, application-specific password or verification code in either list. Its page on deleting a user, read the same day, recommends best practices "like wiping the user's mobile devices, resetting their sign-in cookies, or revoking their security keys" and does not name OAuth tokens or connected apps. Neither page states whether a grant survives suspension or deletion, and this note has not measured it on a live tenant.
Directory.Read.All is a read permission and a broad one. The same Microsoft Graph permissions reference cautions that "Directory permissions grant broad access to directory (Microsoft Entra ID) resources such as user, group, and device in an organization" and advises choosing resource-specific permissions where possible. The Graph credential in this comparison cannot change a grant, and it still reads well beyond the grants under review, so it needs its own access control.
The API controls page, read 2026-09-15, covers a different object. Its four access settings, Trusted, Limited, Specific Google data and Blocked, apply per application, to all users or to selected organizational units, and the page says details about an app typically appear 24 to 48 hours after authorisation. The tokens.list check does not depend on that list. This note does not test a Cloud Identity Free account, does not cover a personal Google Account signing into a corporate resource, and makes no claim about Microsoft Entra beyond the two Graph references cited.
The Microsoft half of the same review, listing a leaver's delegated grants through Graph with Directory.Read.All and flagging high-risk scopes, is set out in the note on the delegated OAuth grant that outlives the employee.
What the scope means when a third-party app asks for it, rather than when an administrator uses it to offboard, is set out in the note on the admin.directory.user.security scope.
Sources and further reading
- Directory API tokens resource
- tokens.list method reference
- tokens.delete method reference
- asps.list method reference
- asps.delete method reference
- verificationCodes.list method reference
- verificationCodes.invalidate method reference
- verificationCodes.generate method reference
- OAuth 2.0 Scopes for Google APIs
- Reports API activities.list method reference
- OAuth Token Audit Activity Events
- Control which apps access Google Workspace data
- Suspend a user temporarily
- Delete or remove a user from your organization
- List oauth2PermissionGrants
- oAuth2PermissionGrant resource type
- Microsoft Graph permissions reference
Turn the procedure into a tenant decision.
The Architecture Workshop maps the checks, side effects, and rollback path to your own Microsoft 365 environment.
Review the workshopMore from Ops Log
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 Microsoft 365 DKIM CNAME target has two documented formats: read it per domain, never build it
Microsoft documents two DKIM CNAME target formats for Microsoft 365 custom domains, one ending in onmicrosoft.com and one in dkim.mail.microsoft. The page splits them by new versus existing custom domain and never mentions tenant age. Read each domain's values with Get-DkimSigningConfig.
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.