← All posts
powershellentra-idautomationsecurity

Catching expiring Entra app credentials before they cause an outage

The built-in Entra recommendation for expiring app credentials only looks 30 days out and lives in a portal tab nobody checks daily. Here is a Microsoft Graph PowerShell runbook that closes that gap.

I’ve lost count of how many “the integration just stopped working” tickets turned out to be an expired client secret on an app registration nobody had looked at since the person who created it left the company. The credential doesn’t fail loudly in advance — it just stops working the moment it expires, usually on a Monday, usually for something that was quietly load-bearing. Entra ID actually ships a built-in recommendation for this. It’s just not enough on its own, and it’s worth understanding exactly why before you build something better on top of it.

What the built-in recommendation actually covers

Microsoft Entra’s Recommendations feature includes applicationCredentialExpiry, which flags app registration credentials — secrets and certificates — expiring within a fixed 30-day window. You’ll find it under Entra ID > Overview > Recommendations in the admin center, or query it directly via Graph:

GET https://graph.microsoft.com/beta/directory/recommendations
GET https://graph.microsoft.com/beta/directory/recommendations/{tenantId}_ApplicationCredentialExpiry/impactedResources

There’s a companion recommendation for service principal credentials, covering the enterprise application side. Both are genuinely useful — but three limitations mean they can’t be your only line of defense:

  • The 30-day window is fixed. You can’t widen it to give an application owner 90 days of lead time, or narrow it to surface only truly urgent cases.
  • Some recommendations, including this one, require an Entra recommendation license (microsoftEntraWorkloadId in the API response) — worth confirming against your own tenant’s licensing before you rely on it as your sole safety net.
  • It’s a portal tab and a Graph endpoint, not a notification. Nothing pushes it to the team that owns the app unless someone remembers to check, or you build the plumbing yourself.

That last point is the one worth solving with automation.

Reading credential expiry with Microsoft Graph PowerShell

Get-MgApplication doesn’t return PasswordCredentials or KeyCredentials unless you ask for the specific application — a full listing gives you the app objects, but you need a second call per app to get its credentials:

Connect-MgGraph -Scopes 'Application.Read.All'

$thresholdDays = 30
$now = Get-Date

$apps = Get-MgApplication -All

$expiring = foreach ($app in $apps) {
    $creds = Get-MgApplication -ApplicationId $app.Id |
        Select-Object -ExpandProperty PasswordCredentials, KeyCredentials -ErrorAction SilentlyContinue

    $allCreds = @($app.PasswordCredentials | ForEach-Object { $_; $_ | Add-Member -NotePropertyName Type -NotePropertyValue 'Secret' -PassThru -Force }) +
                @($app.KeyCredentials      | ForEach-Object { $_; $_ | Add-Member -NotePropertyName Type -NotePropertyValue 'Certificate' -PassThru -Force })

    foreach ($cred in $allCreds) {
        $daysLeft = ($cred.EndDateTime - $now).Days
        if ($daysLeft -le $thresholdDays) {
            [PSCustomObject]@{
                Application = $app.DisplayName
                AppId       = $app.AppId
                CredType    = $cred.Type
                CredName    = $cred.DisplayName
                ExpiresOn   = $cred.EndDateTime
                DaysLeft    = $daysLeft
            }
        }
    }
}

Two things worth knowing before you copy this into a runbook:

  • You can’t filter server-side on credential expiry — $filter doesn’t reach into passwordCredentials/keyCredentials — so this always means pulling every application in the tenant and filtering client-side. On a large tenant, that’s the slow part; budget for it in your Automation runbook’s timeout.
  • Service principals (the enterprise application object, distinct from the app registration) can carry their own credentials too, notably federated identity credentials. Run the same pattern against Get-MgServicePrincipal -All if your tenant relies on those.

Wiring in ownership and an actual notification

The recommendation’s real gap isn’t detection, it’s routing. Get-MgApplicationOwner gets you from an app to the person who should be paged:

foreach ($item in $expiring) {
    $owners = Get-MgApplicationOwner -ApplicationId (
        Get-MgApplication -Filter "appId eq '$($item.AppId)'"
    ).Id
    $item | Add-Member -NotePropertyName OwnerUpn `
        -NotePropertyValue ($owners.AdditionalProperties.userPrincipalName -join '; ')
}

From there, push it somewhere people actually look — a Teams channel via an incoming webhook, an email through Microsoft Graph’s sendMail, or a row in a governance dashboard if you’re already centralizing this kind of signal. The exact sink matters less than the principle: the check has to run unattended, on a schedule, and land somewhere that generates a ticket or a ping — not a portal tab someone has to remember to open.

An Azure Automation runbook with a managed identity (granted Application.Read.All application permission via Graph, since this needs to run without a signed-in user) running daily is enough for most tenants. Keep the threshold configurable as a runbook parameter — 90 days for a first warning, 14 for an escalation — rather than hardcoding Entra’s fixed 30-day window.

The takeaway

The built-in recommendation is a good backstop, not a monitoring strategy — a fixed 30-day window, a license dependency worth double-checking, and no notification path of its own. The Graph PowerShell pattern above costs maybe thirty minutes to wire into a scheduled Automation runbook, and it’s the difference between finding out about an expiring secret from a dashboard you check on your own schedule versus finding out from an incident ticket on a Monday morning.


Sources & further reading: PowerShell sample: export app registrations with expiring secrets and certificates, Recommendation to renew expiring application credentials, Recommendation to renew expiring service principal credentials, Get-MgApplication, Add and manage application credentials in Microsoft Entra ID.