To find expiring Entra app secrets and certificates, connect to Microsoft Graph PowerShell with Application.Read.All, pull every application and service principal with Get-MgApplication -All and Get-MgServicePrincipal -All selecting PasswordCredentials and KeyCredentials, and compare each credential's EndDateTime with today. Export anything that has expired or expires within your rotation window, check the sign-in logs to see which key is actually in use, then add the replacement, update the workload, and remove the old credential.
Who this is for and what you will have
This guide is for Microsoft Entra administrators and platform engineers who own app registrations used by scripts, integrations and line-of-business systems. An expired secret stops a daemon from getting tokens, usually with AADSTS7000222, and a forgotten secret that never expires is a long-lived key an attacker can reuse.
At the end you will have:
- A CSV of every secret and certificate on application objects and service principals, with days left.
- A way to see which credential (by key ID) an app actually signs in with.
- A safe rotation procedure that avoids downtime.
- A tenant policy that limits how long new secrets and certificates can live.
If you're tightening app security more broadly, pair this with controlling which third-party apps users can approve, covered in stopping illicit consent grants.
Where app credentials live
Microsoft Graph stores credentials in two collections, on two kinds of object.
| Collection | Graph type | Typical content | Useful properties |
|---|---|---|---|
passwordCredentials | passwordCredential | Client secrets | keyId, displayName, endDateTime, hint |
keyCredentials | keyCredential | Certificates (public key) | keyId, displayName, endDateTime, type, usage, customKeyIdentifier |
Both collections exist on the application object (what you see under App registrations) and on the service principal (what you see under Enterprise apps). Apps you register in your own tenant normally hold credentials on the application object. SAML signing certificates for enterprise apps live on the service principal, which is why a report that only looks at app registrations misses them.
A few details from the Graph reference that matter for reporting:
endDateTimeis always in UTC.secretTextis only returned when the secret is created. You can't recover it later.hintcontains the first three characters of a secret, which helps you match a secret to the value stored in a key vault or config file.- For a certificate,
customKeyIdentifierdefaults to the thumbprint, and the rawkeyis only returned on single-object requests with$select.
Prerequisites
- The Microsoft Graph PowerShell SDK (
Microsoft.Graph.Applications), plusMicrosoft.Graph.Beta.Reportsfor the sign-in step. - Delegated consent for
Application.Read.Allto read apps and service principals. AuditLog.Read.Allto read sign-in logs. Microsoft notes that downloading sign-in logs through Microsoft Graph requires a Microsoft Entra ID P1 or P2 license.Application.ReadWrite.All(or ownership withApplication.ReadWrite.OwnedByfor app-only use) when you rotate secrets.
Step 1: Report app registration credentials
The script reads every application with only the properties it needs, flattens each secret and certificate into a row, and calculates days remaining.
Connect-MgGraph -Scopes "Application.Read.All"
$windowDays = 60
$nowUtc = [datetime]::UtcNow
function ConvertTo-CredentialRow {
param($Owner, $ObjectType, $Credential, $Kind)
$daysLeft = [math]::Floor(($Credential.EndDateTime - $nowUtc).TotalDays)
[pscustomobject]@{
ObjectType = $ObjectType
DisplayName = $Owner.DisplayName
AppId = $Owner.AppId
ObjectId = $Owner.Id
Kind = $Kind
Name = $Credential.DisplayName
KeyId = $Credential.KeyId
EndUtc = $Credential.EndDateTime
DaysLeft = $daysLeft
Status = if ($daysLeft -lt 0) { 'Expired' } elseif ($daysLeft -le $windowDays) { 'Expiring' } else { 'OK' }
}
}
$apps = Get-MgApplication -All -Property Id, AppId, DisplayName, PasswordCredentials, KeyCredentials
$appRows = foreach ($app in $apps) {
foreach ($s in $app.PasswordCredentials) { ConvertTo-CredentialRow $app 'Application' $s 'Secret' }
foreach ($c in $app.KeyCredentials) { ConvertTo-CredentialRow $app 'Application' $c "Certificate ($($c.Usage))" }
}
$appRows | Sort-Object DaysLeft | Format-Table DisplayName, Kind, Name, EndUtc, DaysLeft, StatusPick a window that matches how long your change process takes. Microsoft's built-in recommendation uses 30 days; 60 leaves room for a change request and a test cycle.
Step 2: Add service principal credentials
Run the same function against service principals, keeping only those that actually hold credentials.
$sps = Get-MgServicePrincipal -All -Property Id, AppId, DisplayName, PasswordCredentials, KeyCredentials |
Where-Object { $_.PasswordCredentials.Count -gt 0 -or $_.KeyCredentials.Count -gt 0 }
$spRows = foreach ($sp in $sps) {
foreach ($s in $sp.PasswordCredentials) { ConvertTo-CredentialRow $sp 'ServicePrincipal' $s 'Secret' }
foreach ($c in $sp.KeyCredentials) { ConvertTo-CredentialRow $sp 'ServicePrincipal' $c "Certificate ($($c.Usage))" }
}
$all = @($appRows) + @($spRows)
$all | Where-Object Status -ne 'OK' |
Sort-Object DaysLeft |
Export-Csv ".\app-credentials-$(Get-Date -Format yyyyMMdd).csv" -NoTypeInformationSecrets on service principals are worth a second look even when they aren't expiring. Microsoft recommends changing services so they use credentials on the backing application object rather than the service principal.
When you review the CSV, look for:
- Expired credentials still present. They no longer authenticate but clutter the inventory. Confirm nothing depends on them and remove them.
- Apps with several overlapping secrets. Often a sign that rotations happened without the old secret being removed.
- Secrets that expire far in the future. Anything past 12 months is outside Microsoft's recommendation.
- Apps with no owner you can identify. Find an owner before the credential expires, not after.
Step 3: Cross-check with Entra recommendations
Microsoft Entra produces two recommendations that cover the same ground:
| Recommendation | API name | What triggers it |
|---|---|---|
| Renew expiring application credentials | applicationCredentialExpiry | A credential on an application registration expiring within the next 30 days |
| Renew expiring service principal credentials | servicePrincipalKeyExpiry | A credential on a service principal expiring within the next 30 days |
Find them in the Microsoft Entra admin center under Entra ID > Overview > Recommendations. Security Reader, Global Reader and Reports Reader can view them; Security Administrator can update them. Credentials that have already lapsed show as completed in the Impacted resources list, so the recommendation is not a substitute for a report that also catches expired items. The sample responses in Microsoft's documentation list microsoftEntraWorkloadId in the requiredLicenses field, so don't depend on the recommendations alone; the script works regardless of licensing.
Step 4: Find out which credential is in use
Before you remove anything, confirm which key the app presents when it signs in. Service principal sign-ins are recorded in the service principal sign-in log, separately from user and managed identity sign-ins. In the beta signIn resource, clientCredentialType shows whether the app used a clientSecret, certificate, federatedIdentityCredential or another type, and servicePrincipalCredentialKeyId and servicePrincipalCredentialThumbprint identify the exact credential.
Connect-MgGraph -Scopes "AuditLog.Read.All"
$appId = "<application (client) ID>"
Get-MgBetaAuditLogSignIn -Filter "signInEventTypes/any(t: t eq 'servicePrincipal') and appId eq '$appId'" -Top 50 |
Select-Object CreatedDateTime, ServicePrincipalName, ClientCredentialType,
ServicePrincipalCredentialKeyId, ServicePrincipalCredentialThumbprint |
Sort-Object CreatedDateTime -DescendingMatch the key ID with the KeyId column in your report. A credential with no recent sign-ins is a removal candidate; a credential that is about to expire and shows sign-ins every hour is your priority.
Step 5: Rotate without downtime
The pattern is always add, switch, verify, remove. Entra allows several credentials on one app at the same time, which is what makes overlap possible.
Rotate a client secret
Connect-MgGraph -Scopes "Application.ReadWrite.All"
$appObjectId = "<application object ID>"
$newSecret = Add-MgApplicationPassword -ApplicationId $appObjectId -PasswordCredential @{
displayName = "rotated-$(Get-Date -Format yyyy-MM)"
endDateTime = (Get-Date).AddMonths(6)
}
# SecretText is only available now - store it in your vault immediately
$newSecret | Select-Object KeyId, Hint, EndDateTimeUpdate the workload's configuration or key vault entry with $newSecret.SecretText, restart or redeploy it, then run the sign-in query from Step 4 and confirm the new KeyId appears. Only then remove the old secret:
Remove-MgApplicationPassword -ApplicationId $appObjectId -KeyId "<old secret keyId>"Note that -ApplicationId takes the object ID of the application, not the application (client) ID.
Rotate a certificate
In the Microsoft Entra admin center, open the app under App registrations, select Certificates & secrets > Certificates > Upload certificate, and upload the new public key as a .cer, .pem or .crt file. Record the thumbprint, deploy the private key to the workload, confirm servicePrincipalCredentialThumbprint in the sign-in log matches the new certificate, and delete the old one. Microsoft recommends using Azure Key Vault to manage certificate access and lifetime in production.
Rotate a SAML signing certificate
For enterprise apps using SAML, open Single sign-on, edit SAML signing certificate, add a new certificate, make it active, verify the application with the relying party, and remove the inactive certificate.
Step 6: Limit how long new credentials can live
Reporting catches problems; policy prevents them. App management policies can restrict new credentials on applications and service principals. Microsoft's Workload ID FAQ lists application management policies as available in the free edition, not only in Workload ID Premium. In the Microsoft Entra admin center, browse to Entra ID > Enterprise apps > Application policies. The relevant settings are:
| Admin center setting | Underlying restriction | Effect |
|---|---|---|
| Block password addition | passwordAddition and symmetricKeyAddition | No new client secrets or symmetric keys |
| Restrict max password lifetime | passwordLifetime and symmetricKeyLifetime | Caps the lifetime of new secrets |
| Restrict max certificate lifetime | asymmetricKeyLifetime | Caps the lifetime of new certificates |
| Block custom passwords | customPasswordAddition | Blocks client-supplied (non-Entra-generated) secrets |
Configuring these needs Security Administrator plus Cloud Application Administrator or Application Administrator, or Global Administrator. Microsoft's tutorial recommends blocking secrets where you can and limiting certificates to 180 days, using restrictForAppsCreatedAfterDateTime to phase restrictions in for newer apps first. Policies don't block token issuance for existing apps; only the create or update operation that violates the policy fails, so you can apply them without breaking running workloads.
Where the workload runs in GitHub Actions, Kubernetes or Azure, a federated identity credential or managed identity removes the secret entirely, which is the best answer to expiry.
Run the report on a schedule
For a weekly job, register a dedicated app with the Application.Read.All application permission and a certificate credential, then connect app-only:
Connect-MgGraph -ClientId "<report app client ID>" -TenantId "<tenant ID>" -CertificateThumbprint "<thumbprint>"The certificate must be in Cert:\CurrentUser\My or Cert:\LocalMachine\My on the machine running the job. Include the report app's own certificate in the output so the job warns you before it locks itself out.
Troubleshooting
AADSTS7000222: The provided client secret keys are expired. The app is presenting a secret whose endDateTime has passed. Create a new secret, update the workload with its value, and then remove the expired one.
AADSTS7000215: Invalid client secret is provided. The value the app sends doesn't match a valid secret on the app registration, for example because it belongs to a secret that was already removed. Compare its first three characters with the hint property of each secret in your report.
The secret value isn't shown in the portal any more. Microsoft only displays the value when it's created. If it wasn't saved, create a new one.
Adding a secret fails after you enabled an app management policy. That's the policy working. Use a certificate, or grant a scoped exception under Application policies while you migrate the workload.
The report is empty for an enterprise app. Its credentials are on the service principal. Make sure Step 2 ran.
Checklist
- Weekly report covering both application objects and service principals.
- Owners identified for every app with an expiring credential.
- Sign-in log check before removing any credential.
- Add, switch, verify, remove for every rotation.
- No secret lifetime over 12 months; certificates preferred.
- Application policies limiting new secret and certificate lifetimes.
- Federated credentials or managed identities used wherever the platform supports them.
References
- passwordCredential resource type
- keyCredential resource type
- Get-MgApplication
- Get-MgServicePrincipal
- Add-MgApplicationPassword
- Remove-MgApplicationPassword
- Recommendation to renew expiring application credentials
- Renew expiring service principal credentials recommendation
- Service principal sign-in logs
- signIn resource type (beta)
- Get-MgBetaAuditLogSignIn
- Add and manage app credentials in Microsoft Entra ID
- Configure restrictions on how applications can be configured
- Enforce secret and certificate standards using application management policies
- Frequently asked questions about Microsoft Entra Workload ID
- Microsoft Entra application management policy API overview
- Use Microsoft Graph PowerShell authentication commands
- Microsoft Entra authentication and authorization error codes