Claude
Skills
Sign in
Back

performing-oauth-scope-minimization-review

Included with Lifetime
$97 forever

Performs OAuth 2.0 scope minimization review to identify over-permissioned third-party application integrations, excessive API scopes, unused token grants, and risky OAuth consent patterns across identity providers and SaaS platforms. Activates for requests involving OAuth scope audit, API permission review, third-party app risk assessment, or consent grant minimization.

Backend & APIsOAuthscope-minimizationAPI-securityconsent-reviewthird-party-risktoken-auditscripts

What this skill does


# Performing OAuth Scope Minimization Review

## When to Use

- Annual or quarterly review of third-party application OAuth permissions
- After a security incident involving compromised OAuth tokens or unauthorized data access
- Compliance audit requiring documentation of third-party data access (GDPR Article 28, SOC 2)
- Discovery of shadow IT applications accessing organizational data via OAuth grants
- Migration or consolidation of SaaS applications requiring permission cleanup
- Implementing least-privilege principle for API integrations

**Do not use** for reviewing first-party application permissions within the same trust boundary; OAuth scope minimization focuses on third-party and cross-boundary consent grants.

## Prerequisites

- Admin access to identity providers (Microsoft Entra ID, Okta, Google Workspace)
- Microsoft Graph API permissions: Application.Read.All, OAuth2PermissionGrant.ReadWrite.All
- Inventory of approved third-party integrations from procurement or IT governance
- OAuth scope risk classification framework
- Tools for token analysis (jwt.io for manual review, automated scripts for bulk analysis)

## Workflow

### Step 1: Inventory All OAuth Grants and Consent Permissions

Enumerate all OAuth application registrations and delegated permissions:

```python
"""
OAuth Grant Inventory - Microsoft Entra ID
Enumerates all application registrations, service principals,
and delegated/application permission grants.
"""
import requests
import json
from collections import defaultdict

class EntraOAuthAuditor:
    def __init__(self, tenant_id, client_id, client_secret):
        self.tenant_id = tenant_id
        self.base_url = "https://graph.microsoft.com/v1.0"
        self.token = self._get_token(client_id, client_secret)
        self.headers = {"Authorization": f"Bearer {self.token}"}

    def _get_token(self, client_id, client_secret):
        url = f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
        response = requests.post(url, data={
            "grant_type": "client_credentials",
            "client_id": client_id,
            "client_secret": client_secret,
            "scope": "https://graph.microsoft.com/.default"
        })
        return response.json()["access_token"]

    def get_all_service_principals(self):
        """Get all service principals (enterprise applications)."""
        apps = []
        url = f"{self.base_url}/servicePrincipals?$top=999&$select=id,appId,displayName,appOwnerOrganizationId,servicePrincipalType,accountEnabled,createdDateTime"

        while url:
            response = requests.get(url, headers=self.headers)
            data = response.json()
            apps.extend(data.get("value", []))
            url = data.get("@odata.nextLink")

        return apps

    def get_oauth2_permission_grants(self):
        """Get all delegated permission grants (user consent)."""
        grants = []
        url = f"{self.base_url}/oauth2PermissionGrants?$top=999"

        while url:
            response = requests.get(url, headers=self.headers)
            data = response.json()
            grants.extend(data.get("value", []))
            url = data.get("@odata.nextLink")

        return grants

    def get_app_role_assignments(self, sp_id):
        """Get application permission assignments for a service principal."""
        url = f"{self.base_url}/servicePrincipals/{sp_id}/appRoleAssignments"
        response = requests.get(url, headers=self.headers)
        return response.json().get("value", [])

    def build_permission_inventory(self):
        """Build comprehensive OAuth permission inventory."""
        service_principals = self.get_all_service_principals()
        delegated_grants = self.get_oauth2_permission_grants()

        # Map service principal IDs to names
        sp_map = {sp["id"]: sp for sp in service_principals}

        inventory = []

        # Process delegated permissions
        for grant in delegated_grants:
            sp = sp_map.get(grant["clientId"], {})
            scopes = grant.get("scope", "").split()

            for scope in scopes:
                if not scope:
                    continue
                inventory.append({
                    "app_name": sp.get("displayName", "Unknown"),
                    "app_id": grant.get("clientId"),
                    "publisher_tenant": sp.get("appOwnerOrganizationId"),
                    "is_third_party": sp.get("appOwnerOrganizationId") != self.tenant_id,
                    "permission_type": "Delegated",
                    "scope": scope,
                    "consent_type": grant.get("consentType"),  # AllPrincipals or Principal
                    "principal_id": grant.get("principalId"),
                    "granted_date": sp.get("createdDateTime"),
                    "is_enabled": sp.get("accountEnabled", True)
                })

        # Process application permissions
        for sp in service_principals:
            app_roles = self.get_app_role_assignments(sp["id"])
            for role in app_roles:
                inventory.append({
                    "app_name": sp.get("displayName"),
                    "app_id": sp.get("id"),
                    "publisher_tenant": sp.get("appOwnerOrganizationId"),
                    "is_third_party": sp.get("appOwnerOrganizationId") != self.tenant_id,
                    "permission_type": "Application",
                    "scope": role.get("appRoleId"),
                    "consent_type": "AdminConsent",
                    "granted_date": role.get("createdDateTime"),
                    "is_enabled": sp.get("accountEnabled", True)
                })

        return inventory
```

### Step 2: Classify OAuth Scopes by Risk Level

Categorize permissions based on data access sensitivity:

```python
"""
OAuth Scope Risk Classification
Maps API scopes to risk levels based on data sensitivity and access breadth.
"""

MICROSOFT_GRAPH_SCOPE_RISK = {
    # CRITICAL - Full administrative or unrestricted access
    "critical": {
        "scopes": [
            "Directory.ReadWrite.All",
            "RoleManagement.ReadWrite.Directory",
            "Application.ReadWrite.All",
            "AppRoleAssignment.ReadWrite.All",
            "Mail.ReadWrite",
            "Mail.Send",
            "Files.ReadWrite.All",
            "Sites.FullControl.All",
            "User.ReadWrite.All",
            "Group.ReadWrite.All",
            "MailboxSettings.ReadWrite",
            "full_access_as_app",
        ],
        "risk_description": "Can read/write all data, modify directory, or impersonate users",
        "review_frequency": "Monthly",
        "requires_admin_consent": True
    },
    # HIGH - Broad read access or sensitive data write
    "high": {
        "scopes": [
            "Mail.Read",
            "Mail.Read.Shared",
            "Calendars.ReadWrite",
            "Contacts.ReadWrite",
            "Files.Read.All",
            "Sites.Read.All",
            "User.Read.All",
            "Group.Read.All",
            "Directory.Read.All",
            "AuditLog.Read.All",
            "SecurityEvents.ReadWrite.All",
            "TeamSettings.ReadWrite.All",
        ],
        "risk_description": "Broad read access to sensitive organizational data",
        "review_frequency": "Quarterly",
        "requires_admin_consent": True
    },
    # MEDIUM - Scoped data access
    "medium": {
        "scopes": [
            "Calendars.Read",
            "Contacts.Read",
            "Files.ReadWrite",
            "Sites.ReadWrite.All",
            "Tasks.ReadWrite",
            "Notes.ReadWrite.All",
            "Chat.ReadWrite",
            "ChannelMessage.Send",
            "Team.ReadBasic.All",
        ],
        "risk_description": "Scoped access to specific data types with write capability",
        "review_frequency": "Semi-annually"
    },
    # LOW - Minimal or user-profile only access
    "low": {
        "scopes": [
            "User.Read",
            "openi

Related in Backend & APIs