Enable App Governance in Defender for Cloud Apps, monitor OAuth-enabled apps accessing Microsoft 365 data, create app policies for overprivileged and suspicious apps, investigate app threats, and enforce app compliance across your tenant.
App Governance extends Microsoft Defender for Cloud Apps with deep visibility into OAuth applications registered in your Microsoft Entra ID tenant. It continuously monitors which apps access Microsoft 365 data (Exchange, SharePoint, OneDrive, Teams), how much data they consume, what permissions they hold, and whether their behavior is anomalous. It detects overprivileged apps, apps from unverified publishers, anomalous data access patterns, and suspicious credential changes. providing the security controls needed to prevent OAuth-based supply-chain attacks and illicit consent grants.
A company discovers 340 OAuth apps with access to their Microsoft 365 data. Of those, 47 have excessive permissions (Mail.ReadWrite, Files.ReadWrite.All) that far exceed their stated purpose. 12 are from unverified publishers with no publisher attestation. 3 apps show anomalous data access. one HR survey tool downloaded 15 GB of SharePoint data overnight. The security team needs comprehensive visibility into all OAuth apps, risk assessment based on permissions and behavior, automated policies to block risky consent, and a remediation workflow to revoke access from compromised or overprivileged applications.
OAuth app attacks bypass traditional perimeter defenses entirely. Attackers use illicit consent grants and malicious app registrations to gain persistent access to email, files, and Teams. without needing user credentials. Once an OAuth app has delegated permissions, it can access data silently in the background, even after the user changes their password or enables MFA. High-profile attacks like the Storm-0558 campaign and Midnight Blizzard’s abuse of OAuth apps demonstrate that app-based attacks are a primary method used by nation-state and financially motivated threat actors. Without App Governance, most organizations have zero visibility into what their OAuth apps are actually doing.
App Governance is a feature within Microsoft Defender for Cloud Apps. You must enable it to begin monitoring OAuth apps in your tenant.
# WHAT: Connect to Microsoft Graph and enumerate all OAuth apps in the tenant
# WHY: Establishes baseline visibility into your app estate before exploring
# App Governance dashboard data
# PERMISSIONS REQUIRED:
# Application.Read.All - read all app registrations and service principals
# Directory.Read.All - read directory objects for publisher verification
Connect-MgGraph -Scopes "Application.Read.All","Directory.Read.All"
# List all app registrations (apps registered IN this tenant)
# These are developer-created apps with client IDs, secrets, and redirect URIs
$apps = Get-MgApplication -All
Write-Host "Total app registrations: $($apps.Count)" -ForegroundColor Cyan
# List service principals (enterprise apps that have been consented to)
# servicePrincipalType eq 'Application' = third-party and first-party apps
# This is the count that matters for App Governance - these apps have actual permissions
$servicePrincipals = Get-MgServicePrincipal -All -Filter "servicePrincipalType eq 'Application'"
Write-Host "Enterprise apps: $($servicePrincipals.Count)" -ForegroundColor Cyan
# List OAuth2 permission grants (delegated permissions consented by users or admins)
# Each grant represents a user or admin consenting an app to access their data
# Scope field contains the actual permissions (e.g., "Mail.Read User.Read")
$grants = Get-MgOAuth2PermissionGrant -All
Write-Host "OAuth permission grants: $($grants.Count)" -ForegroundColor YellowThe App Governance dashboard provides a comprehensive overview of all OAuth apps in your tenant, their privilege levels, data usage patterns, and publisher verification status.
Mail.ReadWrite permission from an unverified publisher that has been inactive for 6 months is a dormant risk. Attackers can compromise the app’s credentials and silently use its existing permissions. Review and clean up inactive apps regularly.Review apps by their permission levels to identify overprivileged applications. apps that have been granted far more access than they need to function.
# WHAT: Identify all OAuth apps with high-risk delegated permissions
# WHY: Overprivileged apps are the #1 risk in OAuth security. An app with
# Mail.ReadWrite.All can read/modify EVERY mailbox in the org.
# This script finds apps whose permissions exceed their business need.
# PERMISSIONS REQUIRED: Application.Read.All, Directory.Read.All
Connect-MgGraph -Scopes "Application.Read.All","Directory.Read.All"
# Define high-risk Microsoft Graph permission scopes
# These permissions grant broad access to organizational data:
# Mail.ReadWrite / Mail.ReadWrite.All - read and modify email (exfiltration risk)
# Files.ReadWrite.All - read/write ALL files in SharePoint/OneDrive
# Directory.ReadWrite.All - modify directory objects (privilege escalation risk)
# User.ReadWrite.All - modify all user accounts
# Sites.ReadWrite.All - full access to all SharePoint sites
# MailboxSettings.ReadWrite - modify mailbox settings (forwarding rule creation)
$highRiskPermissions = @(
"Mail.ReadWrite",
"Mail.ReadWrite.All",
"Files.ReadWrite.All",
"Directory.ReadWrite.All",
"User.ReadWrite.All",
"Sites.ReadWrite.All",
"MailboxSettings.ReadWrite"
)
# Get all service principals (enterprise apps with consent grants)
$servicePrincipals = Get-MgServicePrincipal -All -Filter "servicePrincipalType eq 'Application'"
foreach ($sp in $servicePrincipals) {
# Get delegated permission grants (user-consented or admin-consented scopes)
# These are permissions acting on behalf of a signed-in user
$grants = Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $sp.Id -All
$grantedScopes = ($grants | ForEach-Object { $_.Scope -split " " }) | Sort-Object -Unique
# Check if any granted scopes match our high-risk list
$riskyScopes = $grantedScopes | Where-Object { $_ -in $highRiskPermissions }
if ($riskyScopes.Count -gt 0) {
Write-Host "`n[HIGH RISK] $($sp.DisplayName)" -ForegroundColor Red
Write-Host " App ID: $($sp.AppId)"
# PublisherName - the organization that published the app
Write-Host " Publisher: $($sp.PublisherName)"
# VerifiedPublisher - Microsoft's publisher verification program
# Apps from unverified publishers with high permissions = highest risk
Write-Host " Verified: $($sp.VerifiedPublisher.DisplayName -ne $null)"
Write-Host " Risky Permissions: $($riskyScopes -join ', ')" -ForegroundColor Yellow
}
}
# Also check APPLICATION permissions (app-only / daemon - no user context)
# CRITICAL: App permissions act WITHOUT a user, accessing ALL users' data
# e.g., Mail.ReadWrite.All as app permission = can read EVERY mailbox silently
$appRoleAssignments = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All
foreach ($role in $appRoleAssignments) {
Write-Host " App Role: $($role.AppRoleId) -> $($role.ResourceDisplayName)"
}Mail.ReadWrite.All as an application permission lets the app read every mailbox in the organization.App Governance tracks which Microsoft 365 services each app accesses (Exchange, SharePoint, OneDrive, Teams), how much data it reads or writes, and whether patterns are normal or anomalous.
# Query Microsoft Graph audit logs for app activity
Connect-MgGraph -Scopes "AuditLog.Read.All"
# Get recent sign-in activity for service principals (apps)
$appSignIns = Get-MgAuditLogSignIn -Filter "signInEventTypes/any(t: t eq 'servicePrincipal')" `
-Top 100 -OrderBy "createdDateTime desc"
$appSignIns | Select-Object AppDisplayName, ResourceDisplayName,
CreatedDateTime, IPAddress, Status | Format-Table
# Look for apps accessing Graph API at unusual hours
$appSignIns | Where-Object {
$hour = ([datetime]$_.CreatedDateTime).Hour
$hour -lt 6 -or $hour -gt 22 # Outside business hours
} | Group-Object AppDisplayName | Select-Object Name, Count | Sort-Object Count -Descending
# Check for apps with high-frequency API calls
$appSignIns | Group-Object AppDisplayName |
Select-Object Name, Count |
Sort-Object Count -Descending |
Format-TableApp policies define governance rules for OAuth apps in your tenant. Create compliance policies that alert on or automatically block apps that violate your organization’s security requirements.
Block unverified high-privilege appsAlert on inactive privileged apps# Disable apps from unverified publishers with high-risk permissions
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$servicePrincipals = Get-MgServicePrincipal -All -Filter "servicePrincipalType eq 'Application'"
foreach ($sp in $servicePrincipals) {
# Check if publisher is unverified and app has high-risk grants
if ($sp.VerifiedPublisher.DisplayName -eq $null) {
$grants = Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $sp.Id -All
$scopes = ($grants | ForEach-Object { $_.Scope -split " " }) | Sort-Object -Unique
$hasHighRisk = $scopes | Where-Object { $_ -match "ReadWrite\.All|Mail\.ReadWrite" }
if ($hasHighRisk) {
Write-Host "[ACTION] Disabling: $($sp.DisplayName)" -ForegroundColor Red
Update-MgServicePrincipal -ServicePrincipalId $sp.Id `
-AccountEnabled:$false
}
}
}
# List all disabled apps for audit
Get-MgServicePrincipal -All -Filter "accountEnabled eq false" |
Select-Object DisplayName, AppId, PublisherName | Format-TableThreat detection policies alert you to suspicious app behavior that may indicate a compromised app or malicious OAuth attack.
Anomalous app data access spikeSuspicious consent grant patternAlert on app credential modificationWhen App Governance generates an alert, follow a structured investigation workflow to determine if the app is malicious, compromised, or a false positive.
# Investigate a specific app by App ID
$appId = "YOUR_SUSPICIOUS_APP_ID"
Connect-MgGraph -Scopes "Application.Read.All","AuditLog.Read.All","Directory.Read.All"
# Get app registration details
$app = Get-MgApplication -Filter "appId eq '$appId'"
Write-Host "App Name: $($app.DisplayName)" -ForegroundColor Cyan
Write-Host "Created: $($app.CreatedDateTime)"
Write-Host "Sign-in Audience: $($app.SignInAudience)"
# Get service principal
$sp = Get-MgServicePrincipal -Filter "appId eq '$appId'"
Write-Host "Publisher: $($sp.PublisherName)"
Write-Host "Verified Publisher: $($sp.VerifiedPublisher.DisplayName)"
Write-Host "Account Enabled: $($sp.AccountEnabled)"
# List all permission grants
$grants = Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $sp.Id -All
Write-Host "`n--- Delegated Permissions ---" -ForegroundColor Yellow
foreach ($grant in $grants) {
$resource = Get-MgServicePrincipal -ServicePrincipalId $grant.ResourceId
Write-Host " Resource: $($resource.DisplayName)"
Write-Host " Scopes: $($grant.Scope)"
Write-Host " Consent Type: $($grant.ConsentType)"
Write-Host " Granted To: $($grant.PrincipalId)"
}
# List app role assignments (application permissions)
$appRoles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All
Write-Host "`n--- Application Permissions ---" -ForegroundColor Yellow
foreach ($role in $appRoles) {
Write-Host " Resource: $($role.ResourceDisplayName)"
Write-Host " Role ID: $($role.AppRoleId)"
Write-Host " Created: $($role.CreatedDateTime)"
}
# Check for recently added credentials (persistence indicator)
Write-Host "`n--- Credentials (Secrets & Certificates) ---" -ForegroundColor Yellow
foreach ($cred in $app.PasswordCredentials) {
Write-Host " Secret: $($cred.DisplayName) | Created: $($cred.StartDateTime) | Expires: $($cred.EndDateTime)"
}
foreach ($cert in $app.KeyCredentials) {
Write-Host " Certificate: $($cert.DisplayName) | Created: $($cert.StartDateTime) | Expires: $($cert.EndDateTime)"
}When you confirm an app is malicious, compromised, or overprivileged beyond repair, take decisive remediation actions: disable the app, revoke all consent grants, and block future consent.
# Full remediation workflow for a confirmed malicious app
$appId = "MALICIOUS_APP_ID"
Connect-MgGraph -Scopes "Application.ReadWrite.All","DelegatedPermissionGrant.ReadWrite.All",
"AppRoleAssignment.ReadWrite.All"
# Step 1: Get the service principal
$sp = Get-MgServicePrincipal -Filter "appId eq '$appId'"
Write-Host "Remediating: $($sp.DisplayName) ($appId)" -ForegroundColor Red
# Step 2: Disable the app immediately
Update-MgServicePrincipal -ServicePrincipalId $sp.Id -AccountEnabled:$false
Write-Host "[DONE] App disabled" -ForegroundColor Green
# Step 3: Revoke all delegated permission grants
$grants = Get-MgServicePrincipalOAuth2PermissionGrant -ServicePrincipalId $sp.Id -All
foreach ($grant in $grants) {
Remove-MgOAuth2PermissionGrant -OAuth2PermissionGrantId $grant.Id
Write-Host "[DONE] Revoked grant: $($grant.Scope)" -ForegroundColor Green
}
# Step 4: Remove all application permission (app role) assignments
$appRoles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -All
foreach ($role in $appRoles) {
Remove-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id `
-AppRoleAssignmentId $role.Id
Write-Host "[DONE] Removed app role: $($role.ResourceDisplayName)" -ForegroundColor Green
}
# Step 5: (Optional) Delete the service principal entirely
# Remove-MgServicePrincipal -ServicePrincipalId $sp.Id
# Write-Host "[DONE] Service principal deleted" -ForegroundColor Green
Write-Host "`nRemediation complete. App is disabled and all permissions revoked." -ForegroundColor CyanPrevent future OAuth app risks by controlling how users and admins consent to apps in your Entra ID tenant. Consent policies are the upstream control that stops risky apps before they get permissions.
# Configure consent policy via Microsoft Graph
Connect-MgGraph -Scopes "Policy.ReadWrite.Authorization"
# Get current authorization policy
$policy = Get-MgPolicyAuthorizationPolicy
# Block user consent entirely (require admin consent for all apps)
Update-MgPolicyAuthorizationPolicy `
-DefaultUserRolePermissions @{
PermissionGrantPoliciesAssigned = @() # Empty = no user consent allowed
}
Write-Host "User consent disabled. All apps require admin approval." -ForegroundColor Green
# Alternative: Allow consent only for verified publishers with low-risk permissions
Update-MgPolicyAuthorizationPolicy `
-DefaultUserRolePermissions @{
PermissionGrantPoliciesAssigned = @(
"managePermissionGrantsForSelf.microsoft-user-default-low"
)
}
Write-Host "User consent limited to verified publishers with low-risk permissions." -ForegroundColor Cyan
# Enable admin consent workflow
# Navigate to: Entra ID > Enterprise apps > Admin consent requests > Settings
# Enable: Users can request admin consent to apps they are unable to consent to
# Add reviewers: your security team distribution groupReview what you’ve configured and plan ongoing app governance operations.
# Recommended app governance operations cadence
# Daily: Review App Governance alerts, triage new consent requests
# Weekly: Review newly registered apps, check for unverified publishers
# Monthly: Audit inactive high-privilege apps, review data access trends
# Quarterly: Full app inventory review, consent policy assessment
# Quick daily audit: list new apps registered in last 24 hours
Connect-MgGraph -Scopes "Application.Read.All"
$yesterday = (Get-Date).AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
$newApps = Get-MgApplication -Filter "createdDateTime ge $yesterday"
Write-Host "New app registrations in last 24h: $($newApps.Count)" -ForegroundColor Cyan
$newApps | Select-Object DisplayName, AppId, CreatedDateTime,
@{N="SignInAudience";E={$_.SignInAudience}} | Format-Table
# List apps with expiring secrets (credential rotation needed)
$apps = Get-MgApplication -All
$expiringApps = $apps | Where-Object {
$_.PasswordCredentials | Where-Object {
$_.EndDateTime -lt (Get-Date).AddDays(30) -and $_.EndDateTime -gt (Get-Date)
}
}
Write-Host "Apps with secrets expiring in 30 days: $($expiringApps.Count)" -ForegroundColor Yellow| Resource | Description |
|---|---|
| App Governance overview | Product overview and feature documentation |
| Get started with App Governance | Enable and configure App Governance |
| App Governance policies | Create compliance and threat detection policies |
| Detect and remediate app threats | Investigate and respond to app-based threats |
| Configure user consent settings | Control how users consent to OAuth apps |
| Admin consent workflow | Set up admin approval for app consent requests |
| Publisher verification | Understand app publisher verification in Entra ID |
| App consent grant investigation playbook | Microsoft incident response guidance for illicit consent attacks |