How to Audit OAuth App Grants Before They Become a Breach

··12 min read
How to Audit OAuth App Grants Before They Become a Breach

There is a quiet risk sitting in almost every company's Google Workspace and Microsoft 365 tenant right now: a long list of third-party apps that employees connected months or years ago, each holding a live OAuth token that can read email, browse files, or send messages on someone's behalf. Nobody remembers half of them. Nobody has revoked them. And an attacker who phishes one of those consent screens does not need a password at all.

Here is the uncomfortable stat that should get your attention: in the 2020 SolarWinds fallout, one of the most durable attacker footholds was not a stolen password but abused OAuth application tokens that survived password resets and MFA rollouts. Microsoft's own incident reports have repeatedly flagged illicit consent grants as a top persistence technique. These tokens do not trigger a "new login" alert. They just keep working.

This article walks you through how to audit OAuth app grants before one of them turns into a breach. You will get a repeatable audit process, a scoring method for deciding what to revoke, a worked example with real numbers, a comparison of the major provider consoles, and a maintenance schedule so this never becomes a once-a-decade panic again.

Key Takeaways
  • OAuth grants outlive passwords. Revoking a password or enabling MFA does not kill an existing app token. You must revoke the grant explicitly.
  • Scope is the real risk, not the app name. A grant with mail.read or Files.ReadWrite.All deserves scrutiny even if the vendor looks legitimate.
  • Inventory first, judge second. Export every grant across every identity provider before you start revoking anything.
  • Score each grant on scope sensitivity, publisher trust, last-used date, and user count. Anything scoring high and unused is a fast revoke.
  • Audit on a schedule. A quarterly review plus admin consent controls stops the backlog from rebuilding.

What OAuth App Grants Actually Are (and Why They Leak)

An OAuth app grant is a standing permission. When you click "Allow" on a "Sign in with Google" or "Connect your Microsoft account" screen, you hand a third-party application a token that lets it act against your account within a defined set of scopes. Those scopes range from harmless (openid, email) to genuinely dangerous (https://mail.google.com/ for full Gmail access, or Mail.ReadWrite in Microsoft Graph).

The problem is that these grants are sticky. Once issued, a refresh token can keep minting new access tokens indefinitely, often for months, without any further user interaction. Consider what that means in practice:

  • An employee leaves. Their password is disabled, but a note-taking app they connected in 2022 still holds a token that reads the shared calendar.
  • A marketing intern authorized a "free" analytics tool that requested full mailbox access. The tool got acquired. Nobody re-reviewed the permission.
  • A phishing campaign tricks staff into approving a malicious app disguised as a document viewer. That app now reads inboxes and never once triggered a failed-login alarm.

This is the same category of risk you deal with when you audit browser extension permissions before you install them, and it rhymes with the caution you apply when you safely give AI agents access without sharing passwords. The pattern is identical: a broad, standing, easily-forgotten permission that most people grant with a single click.

OAuth Grant vs Password: Why the Distinction Matters

People conflate "someone has access to my account" with "someone knows my password." They are different, and OAuth grants exploit the gap.

  • A password reset invalidates future logins with the old credential. It does not invalidate an existing OAuth refresh token.
  • MFA protects the interactive login. It does not re-challenge a background app already holding a token.
  • Conditional access location rules apply to sign-ins. Many OAuth token refreshes bypass those checks unless you specifically configure token protection.

That is why "we rotated everyone's passwords" is not a complete incident response. If you skip the grant audit, you may have locked the front door while leaving a dozen delegated keys under the mat.

Step-by-Step: How to Audit OAuth App Grants

Here is the process I run for clients and my own tenants. It works whether you have 10 users or 10,000; the difference is how you export the data.

Step 1: Build a Complete Inventory Across Every Identity Provider

You cannot audit what you cannot see. Start by pulling grants from each place users authorize apps:

  1. Google Workspace: Admin console → Security → API controls → App access control → "Manage Third-Party App Access." Export the list of configured and unconfigured apps.
  2. Microsoft 365 / Entra ID: Entra admin center → Enterprise applications, and separately App registrations. Also check "Consent and permissions." Use the Microsoft Graph PowerShell SDK to export Get-MgOauth2PermissionGrant and Get-MgServicePrincipal.
  3. GitHub org: Organization Settings → Third-party access → OAuth app policy, plus installed GitHub Apps.
  4. Slack, Zoom, Atlassian, Salesforce: each has its own app-management page. Do not skip these; they are where SaaS-to-SaaS tokens hide.

Dump everything into one spreadsheet with columns: App name, Publisher, Scopes, Users granted, Date granted, Last used, Provider.

Step 2: Classify Each Scope by Sensitivity

Not all scopes are equal. Tag each grant's scopes into three tiers:

  • High: full mail access, full Drive/OneDrive read-write, admin directory read, offline access to files, send-as. In Google terms, anything under https://mail.google.com/ or drive; in Graph, Mail.ReadWrite, Files.ReadWrite.All, Directory.Read.All.
  • Medium: read-only calendar, read-only contacts, single-file picker access, profile plus email.
  • Low: openid, email, profile only. Sign-in identity, nothing more.

Step 3: Verify the Publisher

For each app, confirm who actually controls it. Is the publisher verified by the identity provider? Does the app's domain match the vendor you think you're dealing with? A "verified publisher" badge in Entra or a Google-verified OAuth client is a meaningful signal, though not a guarantee.

This is the same ownership-tracing discipline you'd apply when you vet a password manager's ownership before you trust it. An app that reads your entire mailbox deserves at least as much scrutiny as the vault holding your passwords.

Step 4: Cross-Reference Last-Used Data

An app that hasn't touched the API in 180 days but holds high-sensitivity scopes is your top revoke candidate. It provides no ongoing value and carries full risk. Google and Microsoft both surface last-activity data, though you often need Graph or the audit log to get exact dates.

Step 5: Score, Decide, and Revoke

Assign each grant a risk score (I'll show the formula below), sort descending, and work top-down. For anything you revoke, communicate to affected users first if the app is legitimate but simply unused, so you don't break a workflow someone quietly relies on.

Step 6: Lock the Door Behind You

Turn on admin consent workflows so future high-scope grants require approval. In Google, set unconfigured third-party apps to "Blocked" or "Limited." In Entra, disable user consent for apps and route requests through an admin review queue.

A Worked Example: Auditing a 240-User Tenant

Let me make this concrete. A mid-sized agency I'll call NorthBridge had 240 users on Microsoft 365. Their initial export surfaced 63 distinct enterprise applications with active grants. Here's how the numbers broke down.

I scored each grant with a simple formula:

Risk = (Scope tier × 3) + (Users granted ÷ 20) + Staleness points

Where scope tier is 1 (low), 2 (medium), or 3 (high), and staleness adds 3 points if unused for 90+ days, 1 point for 30-90 days, 0 if active.

Applying that across the 63 apps produced a clear picture:

  • 18 apps scored 9 or higher (high scope, stale, or widely granted). These went to the priority review pile.
  • Of those 18, 7 had full mail or file read-write scopes and zero API activity in over 120 days. Immediate revoke.
  • One of the seven was a defunct "email signature generator" that 41 users had authorized in 2021 with Mail.ReadWrite. The vendor's domain no longer resolved. That is exactly the kind of orphaned high-privilege token an attacker loves.
  • The remaining 45 apps scored below 9 and were low-risk sign-in-only integrations, left in place but tagged for the next quarterly review.

Before: 63 apps, 12 holding full mailbox access, no review cadence. After: 47 apps, 4 holding full mailbox access (all verified and active), admin consent required for anything new, and a calendar reminder for the next audit. Total hands-on time: about six hours across two people.

Comparing the Major Provider Audit Consoles

Where you do the audit shapes how hard it is. Here's an honest comparison of the four consoles most teams touch, scored on the criteria that matter during a real audit.

Cover image: Software value feedback loop by jakuza, licensed under BY-SA 2.0 via Openverse.

Provider Grant visibility Last-used data Bulk revoke Admin consent controls Export ease
Google Workspace Good (App access control) Partial, needs audit log Yes, per app Strong (block/limit/trust) CSV export built in

Recent Posts

View all →

Most Popular Software

View all →

Browse by Platform

View all →