Why OAuth applications need a dedicated audit
An application connected to Microsoft 365 can read profiles, files, SharePoint sites, mailboxes, or directory data. With delegated permissions, it acts in the context of a signed-in user. With application permissions, it can operate without a user and continue as long as its identity and consent remain valid.
The risk is not limited to unknown apps. A legitimate integration can become excessive after a vendor change, a new permission, the departure of its owner, or a leaked credential. Valid admin consent confirms that an administrator authorized access; it does not prove the application is still needed or that its identity remains well protected.
This guide provides a triage method and read-only collection examples. It does not replace a complete review of each application's dependencies, code, logs, and actual data access.
Understand the objects before drawing conclusions
The same appId can refer to an application object in its home tenant and a service principal in every customer tenant. Deleting only the app registration or only the enterprise application therefore has different consequences. For a multitenant SaaS app, you normally govern its local service principal and the grants made in your organization.
Delegated permissions are also bounded by the privileges of the user who is present, while an application permission is carried directly by the workload. That distinction should strongly influence priority without implying that delegated access is automatically harmless.
| Object | What it represents | What to review |
|---|---|---|
| App registration | Global definition created in its home tenant | Audience, URIs, requested permissions, owners, secrets and certificates |
| Enterprise application | Service principal, or local instance of the app in your tenant | Enabled state, assignments, grants, owners and activity |
| Delegated grant | Authorization to act for a signed-in user | Scope, user or tenant-wide consent, need for offline_access |
| Application permission | App role usable without a signed-in user | Data scope, admin consent and documented justification |
| Credential | Secret, certificate, managed identity or workload federation | Owner, expiry, storage and rotation |
Define scope and business ownership
Set a decision date as well. An app without ownership or justification should not remain in a queue indefinitely, but a lack of visible activity is not enough evidence to delete it. Some integrations run only at month-end, during disaster recovery, or at annual renewal.
- Which services are in scope: Exchange Online, SharePoint, Teams, OneDrive, Entra ID, Power BI, or custom APIs?
- Is the app internal, Microsoft-published, vendor-supplied, or added directly by a user?
- Who accepts the risk and confirms the business need? A technical owner in Entra is not a substitute for that accountable person.
- How critical is it, which data can it reach, and which operations would stop if access were removed?
- Is it single-tenant or multitenant, interactive or app-only, and used for production, testing, or recovery?
- Which licenses provide access to logs, Defender for Cloud Apps, App governance, or workload identity protection?
Build a read-only PowerShell inventory
The Microsoft Graph PowerShell SDK can export service principals and local app registrations. Use an administrative workstation, a dedicated account, and read-only scopes. Application.Read.All is the documented least-privileged permission for several inventory commands; delegated grants and logs can require additional scopes. Always check the Permissions section for each cmdlet and obtain approval for the privileged session.
The example keeps the two inventories separate instead of assuming that every service principal has a local registration. It changes nothing and exports no secret value.
Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes @(
'Application.Read.All',
'Directory.Read.All',
'DelegatedPermissionGrant.Read.All'
) -NoWelcome
$servicePrincipals = Get-MgServicePrincipal -All -Property @(
'id','appId','displayName','servicePrincipalType','accountEnabled',
'appOwnerOrganizationId','verifiedPublisher','createdDateTime','tags'
)
$applications = Get-MgApplication -All -Property @(
'id','appId','displayName','signInAudience','createdDateTime',
'passwordCredentials','keyCredentials'
)
$servicePrincipals | Select-Object DisplayName,AppId,Id,
ServicePrincipalType,AccountEnabled,AppOwnerOrganizationId,CreatedDateTime |
Export-Csv .\EnterpriseApps.csv -NoTypeInformation -Encoding UTF8
$applications | Select-Object DisplayName,AppId,Id,SignInAudience,
CreatedDateTime,@{N='SecretCount';E={$_.PasswordCredentials.Count}},
@{N='CertificateCount';E={$_.KeyCredentials.Count}} |
Export-Csv .\AppRegistrations.csv -NoTypeInformation -Encoding UTF8Connect owners, delegated grants, and application permissions
Three separate collections are required. Owners tell you who should answer for the app. oAuth2PermissionGrants represent delegated permissions. AppRoleAssignments granted to the service principal represent application permissions. Their identifiers must then be resolved against the target API service principal to produce readable permission names.
For a delegated grant, ConsentType is commonly Principal for one user or AllPrincipals for the tenant. Scope contains a space-delimited set of scopes. For an app role assignment, AppRoleId must be matched to the resource's AppRoles collection; a report containing only GUIDs is of little operational value.
$results = foreach ($sp in $servicePrincipals) {
$owners = @(Get-MgServicePrincipalOwner -ServicePrincipalId $sp.Id -All)
$delegated = @(Get-MgServicePrincipalOauth2PermissionGrant `
-ServicePrincipalId $sp.Id -All)
$appRoles = @(Get-MgServicePrincipalAppRoleAssignment `
-ServicePrincipalId $sp.Id -All)
[pscustomobject]@{
DisplayName = $sp.DisplayName
AppId = $sp.AppId
ObjectId = $sp.Id
Enabled = $sp.AccountEnabled
Owners = ($owners | ForEach-Object {
$_.AdditionalProperties['displayName']
}) -join '; '
DelegatedScopes = ($delegated.Scope | Sort-Object -Unique) -join '; '
TenantWideDelegated = ($delegated.ConsentType -contains 'AllPrincipals')
ApplicationRoleIds = ($appRoles.AppRoleId | Sort-Object -Unique) -join '; '
}
}
$results | Export-Csv .\OAuthPermissions.csv `
-NoTypeInformation -Encoding UTF8Interpret risk without a simplistic blacklist
These signals prioritize review; they do not label an app as malicious. A Microsoft 365 backup platform may legitimately require broad access. That access should be matched by clear ownership, strong credential architecture, logging, contractual controls, and a review cadence proportional to its reach.
Publisher verification improves provenance, but it does not certify code or future behavior. Conversely, an unverified internal app is not automatically dangerous. Context and evidence remain decisive.
| Signal | Why review it | Validation question |
|---|---|---|
| Broad application permission | Unattended access to large data sets | Would a Selected or resource-specific permission work? |
| Mail.ReadWrite, Files.ReadWrite.All, or Sites.FullControl.All | Read or change business content | Which mailboxes, sites, and operations are actually required? |
| Directory.ReadWrite.All or RoleManagement.ReadWrite.Directory | Change directory objects or roles | Can the vendor demonstrate every privileged operation? |
| AllPrincipals consent | Delegated scope across the tenant | Must every user be able to use the application? |
| No owner or unverified publisher | Harder to establish accountability and provenance | Who confirms the publisher, contract, and need? |
| Long-lived or expiring secret | Credential theft or outage risk | Can managed identity, federation, or a certificate be used? |
| Inactive according to one source | Logs may be incomplete or outside retention | Is there a rare job, another log, or an external dependency? |
Audit secrets, certificates, and identities without exposing them
Review each credential's type, expiry, owner, execution location, and rotation procedure. Never export secret values into a report. Microsoft only displays a client secret value when it is created.
For an Azure workload, prefer managed identity. For a compatible external platform, workload identity federation avoids a shared secret. When neither option fits, Microsoft recommends certificate credentials ahead of client secrets. A certificate remains an operational secret: its private key, storage, rotation, and monitoring still require governance.
$credentialReport = foreach ($app in $applications) {
foreach ($secret in $app.PasswordCredentials) {
[pscustomobject]@{
App=$app.DisplayName; AppId=$app.AppId; Type='ClientSecret'
CredentialId=$secret.KeyId; Start=$secret.StartDateTime
End=$secret.EndDateTime
}
}
foreach ($cert in $app.KeyCredentials) {
[pscustomobject]@{
App=$app.DisplayName; AppId=$app.AppId; Type='Certificate'
CredentialId=$cert.KeyId; Start=$cert.StartDateTime
End=$cert.EndDateTime
}
}
}
$credentialReport | Sort-Object End |
Export-Csv .\OAuthCredentialExpiry.csv -NoTypeInformation -Encoding UTF8Use logs without treating missing evidence as evidence of no use
Service principal sign-in logs show nonuser authentication, including certificate and client-secret flows. Events are grouped when principal, status, IP address, and resource match; expand the row to see individual timestamps. Managed identity sign-ins appear in their own category.
Audit logs can reveal creation of a service principal, addition of a grant or app role, and credential changes. Availability, retention, and access depend on licensing and export configuration. For critical apps, route the required categories to Log Analytics or a SIEM according to your retention policy.
Never classify an app as unused solely from its creation date, last modification, or a short log window. Correlate sign-ins, audit records, owners, billing, documentation, network flows, and the business calendar.
Prioritize apps and produce an action plan
At minimum, the report should include name, appId, objectId, publisher tenant, audience, status, technical and business owners, resolved permissions, consent type, credential type and expiry, last demonstrable activity, criticality, decision, approver, and next review date.
Keep raw evidence separate from the executive summary. IDs, permissions, and events are essential to investigation, but they can also reveal architecture and should be protected.
| Priority | Example finding | Action |
|---|---|---|
| P1 — immediate | Unknown app, broad access, suspicious activity, or unexplained new credential | Preserve evidence, contain access, engage incident response |
| P2 — high | Excessive application permission, no owner, poorly managed secret | Assign ownership, reduce scope, replace the credential, and test |
| P3 — planned | Legitimate app with incomplete documentation or rotation | Document it, set a deadline, and add it to periodic review |
| Monitor | Justified app, least privilege, owners and logs in place | Monitor permission, owner, and credential changes |
Remove access through a controlled process
Do not begin by deleting the app registration. Confirm the exact object, owner, dependencies, permissions, and rollback path. To contain a compromised app, disabling its service principal may be faster and reversible; to correct excessive scope, remove the precise grant or app role assignment. Rotating a credential does not automatically cancel every existing token and consent.
After the change, test both required functionality and expected denials, then watch for errors and sign-ins. Delete the service principal or app registration only after an observation period and owner confirmation. A multitenant application can continue to exist with its publisher even after its local instance is removed.
- Preserve logs and grant details before acting when compromise is suspected.
- Temporarily disable the service principal if a controlled interruption is acceptable.
- Remove only the delegated grant or app role assignment that is no longer required.
- Revoke or replace the compromised credential and update the workload.
- Validate business scenarios, expected denials, and alerts after the change.
- Document the decision, approver, rollback, and final deletion.
Reduce the next consent risk
The admin consent workflow gives users a controlled way to request an app instead of bypassing the process. It does not replace analysis: reviewers need to understand the permission, data, delegated or app-only mode, vendor, owner, and credential model before approving it.
Defender for Cloud Apps and App governance can enrich the analysis of OAuth apps and behavior when the corresponding licenses are available. Those signals accelerate triage, but the decision still needs business context and dependency validation.
- Limit user consent to low-impact permissions and applications that meet policy.
- Enable an admin consent request workflow with defined reviewers, context, and deadlines.
- Require both technical and business ownership before approval.
- Prefer Selected or resource-specific permissions over tenant-wide access when they meet the need.
- Monitor new credentials, grants, app roles, and anomalous sign-ins.
- Review applications periodically, including those from established vendors.
Reusable checklist
- Export app registrations, service principals, enabled state, and owner tenant.
- Resolve technical owners and confirm the accountable business owner.
- Resolve every delegated and application permission to readable names.
- Identify consent type, assignments, and affected users.
- Inventory secrets, certificates, federation, dates, and rotation procedures without exporting secrets.
- Correlate sign-ins, audit, SIEM, contracts, billing, and business schedules.
- Rank risk by scope, data, activity, provenance, credential, and criticality.
- Approve every reduction or removal with tests and rollback.
- Monitor after the change and set the next review.
The goal is continuous governance, not an annual cleanup
A good audit does more than find apps to delete. It makes every access explainable: who owns it, why it exists, which data it reaches, which identity it uses, how long it remains valid, and how removal will be tested.
The scripts above speed up inventory, but they cannot see the contract, business process, vendor code, or every dependency. A full Microsoft 365 assessment puts OAuth permissions in context with roles, sharing, Conditional Access, privileged accounts, logging, and incident response.