Short answer: To monitor Entra app secret and certificate expiration, read Microsoft Graph credential dates on a schedule. Filter them against your warning threshold. Send the results by email, or ingest them into your own Log Analytics table. An Azure Monitor log search alert can query that table at your chosen frequency.
This guide builds an expiry inventory and alert pipeline. For Microsoft's existing warning mechanisms, see Azure app secret expiration notifications.
What to inventory
Check both application objects and service principal objects. Their credentials are separate collections. The password credential reference and key credential reference describe credentials on both object types.
| Inventory | Fields to read | Why include it | Source |
| ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App registrations | passwordCredentials and keyCredentials | Client secrets and application certificates. | List applications |
| Service principals | passwordCredentials and keyCredentials | Credentials on the service principal, including the SAML signing certificate renewal path. | List service principals, service principal renewal |
| Application owners | /applications/{id}/owners | Ownership information for your routing plan. | List application owners |
Both credential types expose keyId and endDateTime in UTC. See the password properties and key properties. Graph returns a generated secret value only during its initial creation request. You cannot retrieve it later through the password credential API.
Treat an empty owner list as work to assign, using the ownerless credential guide. This inventory example excludes Application Proxy SSL certificates. Use the Application Proxy SSL renewal guide for that separate check.
Read expiry dates with Microsoft Graph
Application.Read.All is the least privileged permission for listing applications and service principals. Both API references support selecting the needed properties:
GET https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials
GET https://graph.microsoft.com/v1.0/servicePrincipals?$select=id,appId,displayName,passwordCredentials,keyCredentials
Follow every @odata.nextLink until no next page remains, as described in Graph paging. Filter endDateTime in your script. The application resource reference lists $filter support for keyCredentials but lists none for passwordCredentials. Local filtering gives both collections the same threshold logic.
The PowerShell example below reads app registrations only. It adapts the commands in Microsoft's export sample. Run it in an authenticated Microsoft Graph PowerShell session. For a scheduled run, configure authentication as described in the next section.
$WarningDays = 30
$Now = [DateTimeOffset]::UtcNow
$Cutoff = $Now.AddDays($WarningDays)
$Properties = @('id', 'appId', 'displayName', 'passwordCredentials', 'keyCredentials')
Get-MgApplication -All -Property $Properties -ErrorAction Stop | ForEach-Object {
$App = $_
foreach ($Type in @('PasswordCredentials', 'KeyCredentials')) {
foreach ($Credential in $App.$Type) {
if ($null -eq $Credential.EndDateTime) { continue }
# Graph returns endDateTime in UTC
$End = [DateTimeOffset][DateTime]::SpecifyKind($Credential.EndDateTime, 'Utc')
if ($End -le $Cutoff) {
[pscustomobject]@{
AppDisplayName = $App.DisplayName
AppId = $App.AppId
CredentialType = $Type
KeyId = $Credential.KeyId
DisplayName = $Credential.DisplayName
EndDateTime = $End.UtcDateTime.ToString('o')
DaysLeft = [math]::Floor(($End - $Now).TotalDays)
}
}
}
}
}
Change $WarningDays to your chosen lead time. This example includes expired credentials, which have negative DaysLeft. It skips missing expiry dates, so review those separately. For service principal coverage, repeat the same collection logic with Get-MgServicePrincipal -All -Property $Properties, shown in the service principal API's PowerShell example. Add the object ID and object type to your stored rows to keep both inventories distinct.
For owner lookup and CSV output, use the fuller interactive export sample. Keep the pipeline's output limited to the metadata fields you need.
Run it on a schedule
An Azure Automation runbook can use a system-assigned managed identity. Grant that identity the Graph application role Application.Read.All using Microsoft's managed identity app-role assignment guide. The runbook then needs no client secret of its own, so the monitor has no secret to rotate.
Alternatively, use a Logic Apps Recurrence trigger. Microsoft says it skips missed recurrences and resumes at the next scheduled interval. See recurring workflow schedules.
A daily run is enough for thresholds measured in days. Record the last successful collection time and alert separately when collection fails. A failed read must not look like an organization with no expiring credentials.
Alert in Azure Monitor
Create your own Log Analytics custom table before sending data. Use the Logs Ingestion API with a data collection rule that maps your JSON rows into the table. Custom table names must end in _CL.
For this example, create AppCredentialExpiry_CL as your own table and populate it from your runbook. This is not a Microsoft-provided credential table. Define TimeGenerated and EndDateTime as datetime columns, DaysLeft as a numeric column, and the other columns below as strings.
Ingest a complete credential snapshot on each successful run, including credentials outside the warning window. Remove the PowerShell example's cutoff condition for this ingestion path. Send TimeGenerated as the collection time. Keep the alert query's lookback long enough to include a successful scheduled run.
This KQL example selects the latest row per app, credential type and key ID, then applies a 30-day threshold:
AppCredentialExpiry_CL
| where TimeGenerated > ago(2d)
| summarize arg_max(TimeGenerated, *) by AppId, CredentialType, KeyId
| extend DaysLeft = (EndDateTime - now()) / 1d
| where DaysLeft <= 30
| project TimeGenerated, AppDisplayName, AppId, CredentialType, KeyId, EndDateTime, DaysLeft
| order by EndDateTime asc
The query recalculates days left at evaluation time. This avoids depending on the age of the stored DaysLeft value. For a combined application and service principal inventory, add object ID and object type to the table and grouping columns.
Follow Create a log search alert rule. Select your workspace, enter the query, and set the evaluation frequency. Configure the rule to fire when the query returns more than zero rows, then select the action group for notification delivery.
An empty result does not prove collection succeeded. Keep the collection failure alert separate. Deleted credentials can remain in earlier snapshots until they leave the query window. Before renewing, confirm that each credential still exists in the latest complete inventory. For a stricter pipeline, query only the latest successful snapshot and track its completion separately.
If you prefer direct email, use Graph sendMail from your runbook. It needs the Mail.Send permission and sends as the user you call it for. Choose that delivery path instead of maintaining a Log Analytics table and alert rule.
Pick thresholds and owners
Consider reminders at 60, 30 and 7 days. Microsoft uses that timing for SAML signing certificate emails. Here it is an editorial planning example for your monitor, not a Microsoft recommendation for app secrets.
Choose thresholds that leave room for approvals and deployment. Use lead time and business-day math. Track which threshold you already sent for each key ID, so scheduled runs do not produce unwanted repeat emails.
Route warnings to a reachable owner and a shared admin address. Plan a fallback when an owner leaves. Store the intended recipients, send time and delivery result if you need evidence of each attempt.
After an alert fires
Microsoft's application credential action plan adds a replacement, updates the application code, verifies that the application works, then removes the old credential. Keep that overlap while you change each consumer.
Use Client secret expired or Client authentication certificate expired for the renewal steps. Before removing the old credential, follow Identify the active credential to confirm the new key ID in fresh successful sign-ins. For SAML, follow the SAML signing certificate guide.
Where AppAlert fits
AppAlert is a hosted alternative to maintaining this runbook. Its credential coverage includes client secrets, application certificates, SAML signing certificates and Application Proxy SSL certificates. Email alerts go to configured admin recipients, with optional owner routing and fallback to admins. Notification Audit records each attempt.
Compare Microsoft's built-in warnings, a DIY monitor and AppAlert side by side.
Related links
- Microsoft's PowerShell export sample
- Microsoft Graph paging
- Azure Monitor Logs Ingestion API
- Create an Azure Monitor log search alert
- Microsoft's expiry notification mechanisms
- Identify the active credential
- Renewal guides
AppAlert reads credential metadata and never receives raw secret values or private keys. Use the scheduled approach above when you want to maintain your own inventory and delivery process.