OAuth app access review is the control most mid-market SOC 2 programs forget until the auditor asks who authorized a Slack bot with channels:history or a Google Workspace add-on with domain-wide mail read. Humans get quarterly campaigns. Non-human OAuth grants accumulate in admin consoles as "apps" with a consent screenshot from onboarding and no owner. That is not CC6 evidence. That is a museum of yes-clicks.
Rutagon builds the recertification loop: inventory grants, attach scopes and last-used, assign an owner, decide retain or revoke, and execute revoke through the vendor API. The GRC tool you already run still hosts the campaign. We do not replace it. We do not claim Rutagon holds a SOC 2 report. We make the grant list sampleable.
Why Consent Screenshots Fail CC6
A screenshot of an OAuth consent screen proves someone clicked Allow once. It does not prove the grant is still appropriate, that the scopes match the job, that the app still exists, or that anyone owns it this quarter. NIST SP 800-53 Rev. 5 AC-2 expects account and access review against current requirements (NIST SP 800-53 Rev. 5). An OAuth grant is access. It belongs in the same family as a GitHub org role.
The blast radius is often larger than a human admin. A Workspace app with https://mail.google.com/ or a Microsoft Graph application with Mail.Read and Directory.Read.All can export more than any one employee's mailbox. Slack workspace tokens with classic scopes still show up in companies that never finished the granular-scope migration. Those grants outlive the contractor who installed them.
Verizon's Data Breach Investigations Report has repeatedly treated credential and token abuse as a primary path (Verizon DBIR). An OAuth token is a credential. Reviewing people and ignoring apps is how the sample finds the hole.
OAuth App Access Review We Actually Run
We snapshot three surfaces first — they cover most 50–500 person SaaS companies:
- Google Workspace: OAuth apps and domain-wide delegation clients
- Microsoft 365 / Entra: enterprise applications and OAuth2 permission grants
- Slack: workspace apps and legacy tokens if any remain
Each row becomes {app_id, display_name, scopes, owner, installed_at, last_used, publisher}. Last-used comes from admin audit logs or Graph sign-in activity, not from folklore. Missing last-used is a flag, not a pass.
def recertify_grant(grant, owner_decision):
evidence = {
"app": grant.app_id,
"scopes": sorted(grant.scopes),
"owner": grant.owner_email,
"last_used": grant.last_used,
"decision": owner_decision.action,
"note": owner_decision.note,
"decided_at": utcnow().isoformat(),
}
if owner_decision.action == "revoke":
result = connectors[grant.platform].revoke(grant.app_id)
evidence["revoke_status"] = result.status
seal(evidence)
return evidence
Revoke is an API call. A Jira ticket that says "please remove the DocuSign app" is not closed evidence until the grant is gone in a follow-up snapshot.
This sits next to user access review automation and contractor and vendor access review. Capability: security automation. Same-batch guest work is Microsoft 365 guest access review.
Owners, Scopes, and the "IT Installed It" Problem
Every grant needs a named human, not "IT" and not the vendor's support alias. If the app is a company-wide integration (payroll, HRIS, backup), the owner is the business system owner. If nobody will claim it, the default decision is revoke after a watch window — not retain because it looks familiar.
Scope review is the second pass. We do not ask reviewers to parse OAuth URI strings unaided. We map scopes to a short blast-radius label: mail-read, drive-write, admin, chat-history, identity-write. A grant that only needed calendar-read and still holds mail-read is a reduce-scope item, not a binary retain. Some vendors allow incremental admin consent; some require uninstall and reinstall. We document which, so the auditor is not told a story the API cannot support.
Publisher verification is a third flag, not a control by itself. An unverified app with admin scopes is a conversation. A verified publisher with unused admin scopes is still unused admin scopes.
Last-Used and the Dormant Bot
Dormant grants are the easy wins. No sign-in or token use in 90 days, and not a documented break-glass integration, becomes a revoke candidate. We exclude apps that only run on a quarterly schedule — payroll export, board pack — by a named exception, not by ignoring last-used entirely.
For Slack, we still see classic bots that never moved to granular scopes. Those get their own campaign track. For Workspace domain-wide delegation, we treat the service account client as privileged access, same tightness as privileged access review for AWS.
Evidence the Auditor Can Sample
A closed OAuth campaign produces:
- Population completeness: the admin-API list, not a volunteer spreadsheet
- Decision log: retain / reduce-scope / revoke, with notes
- Remediation: API status and a later snapshot without the grant
- Residual: apps that cannot be revoked via API, listed honestly
We push those artifacts into the existing GRC custom test for logical access. We do not pitch a cheaper GRC. We do not educate a competitor on how to productize an OAuth IGA. We show you the grant list from your last admin console export and mark what a campaign would close.
Internal OAuth clients — the ones your own SPA registered in Entra or Google — belong in the same population. Teams skip them because "we built it." Those clients often have the widest Graph or Workspace scopes because someone checked every box during a spike. Recertify them. Reduce scopes when the SPA only needs User.Read. Delete clients for products you shut down.
A first campaign usually takes one quarter to get honest last-used. After that the job is calendar. If last-used is missing for a platform, we label those rows "unknown" and we do not auto-retain unknown-plus-admin-scopes. Unknown is a data-quality ticket, not a pass.
See what an Access & Credential Governance Diagnostic finds in your environment →
907-841-8407 · contact@rutagon.com
Frequently Asked Questions
Does OAuth app access review replace our user access campaign?
No. People and apps are different populations. Run both. A clean Okta campaign with a forgotten Graph application is still a CC6 miss.
Can we rely on Microsoft or Google's built-in app review?
Use them when they produce a complete population, last-used, and a revoke that we can evidence. Many built-in views are admin UX, not a sealed quarterly artifact. We wrap them or replace the evidence shape, not the identity platform.
What if a vendor has no revoke API?
We document the residual, store the admin-console export, and put a human procedure on it. We do not mark the control automated.
Are marketplace apps safer than internal OAuth clients?
Marketplace listing is not least privilege. We still recertify scopes and owners. Internal clients often have broader grants because someone used "add all Graph permissions" during a spike.
Will this work if we already have a GRC OAuth connector?
If the connector returns scopes, owners, and last-used for every in-scope platform, we leave it. If it only lists app names, we build the rest.