azureblog.co.uk
← cd ~/posts

Find expiring app secrets and certificates before they bite

Expired client secrets cause some of the most avoidable outages in Entra. A short Graph script shows what's about to expire across the tenant.

2 min read⚠ checked 29 Oct 2025Entra ID · PowerShell
On this page
  1. The script
  2. Making it useful
  3. Running it weekly with Azure Automation
  4. What to do with the list

authenticate with client secrets or certificates, and both expire. When one does, an integration quietly stops working, often on a Monday morning. Entra doesn't email the app owner by default, so you need your own early warning.

Client secret

  • A password for the app
  • Expires and must be rotated
  • Easily copied into scripts

Certificate

  • Private key stays on the host
  • Still expires
  • Harder to leak by accident

Managed identity / federation

  • No credential to store
  • No expiry to manage
  • The preferred option where possible
Three ways an app can authenticate, from most to least maintenance.

The script

This lists every secret and certificate on every app registration, with the days remaining, sorted so the most urgent are first:

powershell
Connect-MgGraph -Scopes "Application.Read.All"

$days = 60
$now  = Get-Date

Get-MgApplication -All -Property DisplayName,AppId,PasswordCredentials,KeyCredentials |
  ForEach-Object {
    $app = $_
    $creds = @(
      $app.PasswordCredentials | ForEach-Object { [pscustomobject]@{ Type="Secret";      Name=$_.DisplayName; End=$_.EndDateTime } }
      $app.KeyCredentials      | ForEach-Object { [pscustomobject]@{ Type="Certificate"; Name=$_.DisplayName; End=$_.EndDateTime } }
    )
    foreach ($c in $creds) {
      [pscustomobject]@{
        App      = $app.DisplayName
        AppId    = $app.AppId
        Type     = $c.Type
        Name     = $c.Name
        Expires  = $c.End
        DaysLeft = [int]($c.End - $now).TotalDays
      }
    }
  } |
  Where-Object DaysLeft -le $days |
  Sort-Object DaysLeft |
  Format-Table -AutoSize

Negative DaysLeft values are already expired. Those are worth a look too: an expired secret that nobody noticed might mean the app is no longer used and can be cleaned up.

Making it useful

  • Export it. Swap Format-Table for Export-Csv and share it with app owners.
  • Run it on a schedule. An Azure Automation runbook with a granted Application.Read.All can run it weekly and email the results.
  • Set owners. Every app registration should have at least two owners, so there's always someone to chase.
  • Prefer certificates or managed identities. Shorter secret lifetimes are safer, but managed identities remove the problem entirely for Azure-hosted workloads.

Running it weekly with Azure Automation

  1. Create an Automation account with a system-assigned managed identity.
  2. Grant the identity the application permission Application.Read.All.
  3. Add the Microsoft.Graph.Authentication and Microsoft.Graph.Applications modules to the account.
  4. Create a PowerShell runbook with the script, replacing Connect-MgGraph -Scopes … with Connect-MgGraph -Identity.
  5. Schedule it weekly and send the results to a shared mailbox or Teams channel.
ScheduleRunbookMicrosoft GraphTeam mailboxMonday 07:001Connect-MgGraph-Identity2Get-MgApplication -All3Filter credentials expiring in60 daysEmail the list5
The weekly check, unattended.

What to do with the list

  • Expiring soon: contact the owner and agree a rotation date.
  • Already expired: check whether the app is still used before renewing; it may be safe to delete.
  • No owner: find one before the next expiry, or plan to retire the app.

This post was last checked against Microsoft's documentation over six months ago. The approach should still hold, but check the linked sources for anything that has changed before you act on it.

Next in Automating Entra and Azure safely · part 5 of 5Six KQL queries for Entra sign-in logs every admin should keep →