Two failure modes share one root cause. A client secret that expires quietly takes a nightly job or an integration down with it. A client secret created with the longest lifetime the portal allowed, on an app registration nobody owns any more, is a standing credential for whoever holds it. Microsoft's own framing covers both: stored secrets and certificates are a security risk, must be stored securely and rotated, and cause downtime when they expire.
Most write-ups stop at the export script. This guide gives you the script in Microsoft Graph PowerShell and in raw Graph, then the four steps that turn the export into a fixed tenant: ownership, dependency mapping, rotation without downtime, and stopping the problem from coming back. It is part of the Orbitra guides on privileged identity in Entra ID and Azure.
What you are looking for
A confidential client app registration can carry three kinds of credential: client secrets, certificates, and federated identity credentials. Only the first two expire. Microsoft's position on secrets is stricter than most tenants' practice: convenient for local development, less secure than certificates or federated credentials, and something that "should not be used in production environments". So the inventory below is really three lists: credentials already expired, credentials expiring soon, and secrets on production apps at all.
Two things to keep in mind. The secret value is shown once, at creation, and never again, so rotation always means creating a new secret. And changes to an application object are reflected in its service principal only in the home tenant, so for a multitenant app the credential lives on the app registration in the tenant that published it.
Step 1: List every secret and certificate with its expiry
Run this with Microsoft Graph PowerShell. It walks every app registration, flattens each credential into one row, and marks it expired, expiring within 30 days, or fine. Change $horizon to match your change window. Keep the KeyId column: it is how you will later prove which credential an application is actually using.
Connect-MgGraph -Scopes "Application.Read.All"
$now = Get-Date
$horizon = $now.AddDays(30)
function Row($app, $c, $kind) {
[pscustomobject]@{
App = $app.DisplayName
AppId = $app.AppId
ObjectId = $app.Id
Kind = $kind
KeyId = $c.KeyId
Name = $c.DisplayName
Created = $c.StartDateTime
Expires = $c.EndDateTime
Status = if ($c.EndDateTime -lt $now) { "expired" }
elseif ($c.EndDateTime -lt $horizon) { "expiring" }
else { "ok" }
}
}
$rows = foreach ($app in Get-MgApplication -All -Property Id,AppId,DisplayName,PasswordCredentials,KeyCredentials) {
foreach ($c in $app.PasswordCredentials) { Row $app $c "secret" }
foreach ($c in $app.KeyCredentials) { Row $app $c "certificate" }
}
$rows | Sort-Object Expires | Export-Csv .\app-credentials.csv -NoTypeInformation
$rows | Where-Object Status -ne "ok" | Format-Table App, Kind, Name, Expires, Status
The same data in raw Graph, for anyone scripting outside PowerShell: page through GET https://graph.microsoft.com/v1.0/applications?$select=id,appId,displayName,passwordCredentials,keyCredentials&$top=999, follow @odata.nextLink until it is absent, and read endDateTime and keyId from each entry in passwordCredentials and keyCredentials.
Step 2: Find who owns each app registration
An expiring secret with an owner is a ticket. An expiring secret with no owner is a guess about which team, which server, and which job depends on it. Ownerless app registrations are also where stale, long-lived secrets accumulate, because nobody is left to notice them. Add owners to the export:
$owners = foreach ($app in Get-MgApplication -All -Property Id,DisplayName) {
$o = Get-MgApplicationOwner -ApplicationId $app.Id -All
$names = foreach ($x in $o) {
if ($x.AdditionalProperties.userPrincipalName) { $x.AdditionalProperties.userPrincipalName } else { $x.Id }
}
[pscustomobject]@{
App = $app.DisplayName
ObjectId = $app.Id
Owners = if ($names) { $names -join "; " } else { "NONE" }
}
}
$owners | Where-Object Owners -eq "NONE"
In raw Graph the equivalent is GET https://graph.microsoft.com/v1.0/applications/{id}/owners for each application. For every app registration that comes back with no owner, before you touch its credentials: check the audit log and your deployment records for who last changed it, assign at least two owners, and record in the app's notes what it is for. If nobody claims it after a reasonable search and the sign-in logs in the next step show no activity, treat it as a candidate for removal rather than rotation.
Step 3: Map which workloads depend on each secret
You cannot rotate safely until you know what signs in with the credential. The Entra sign-in logs hold four log types: interactive user, non-interactive user, service principal, and managed identity sign-ins; the legacy view shows only interactive user sign-ins, so open the service principal sign-ins under Entra ID, Monitoring & health, Sign-in logs (Reports Reader is the least privileged role that can read them). Usage & insights in the same section adds two reports built for this job: "Service principal sign-in activity" and "Application credential activity".
The question that matters for rotation, which key ID each application is presenting, needs the beta sign-in API. The v1.0 signIn resource does not expose servicePrincipalId, servicePrincipalCredentialKeyId, or servicePrincipalCredentialThumbprint; those are beta only. servicePrincipalCredentialKeyId is the key credential ID the service principal used to authenticate; servicePrincipalCredentialThumbprint is the certificate thumbprint. Graph SDKs default to v1.0, so the beta module is required. Pull two weeks of service principal sign-ins and group them:
Import-Module Microsoft.Graph.Beta.Reports
Connect-MgGraph -Scopes "AuditLog.Read.All" # Reports Reader or higher
$since = (Get-Date).AddDays(-14).ToString("yyyy-MM-ddTHH:mm:ssZ")
$spSignIns = Get-MgBetaAuditLogSignIn -All -Filter "createdDateTime ge $since and signInEventTypes/any(t: t eq 'servicePrincipal')"
$spSignIns |
Group-Object AppId, ServicePrincipalCredentialKeyId |
Select-Object Count, Name |
Sort-Object Name
The raw request is GET https://graph.microsoft.com/beta/auditLogs/signIns?$filter=createdDateTime ge 2026-08-20T00:00:00Z and signInEventTypes/any(t: t eq 'servicePrincipal'). Join on AppId. Every application in your expiring list should appear with the key ID it presents, plus source addresses and target resources, which tell you where it runs and what it reaches. No sign-ins in two weeks may mean dead, or may mean monthly; check before you decide. A key ID that is not in your inventory is a finding in its own right: a credential exists on the service principal object rather than the application object, or your export missed something. Microsoft's remediation guidance for a risky workload identity starts exactly here, with an inventory of all credentials on both objects.
Step 4: Rotate in the safe order
Rotation breaks things when the old secret is removed before the new one is in use. The order that avoids downtime has four steps, and the third is the one usually skipped.
- Add a second credential. Create the new secret (or, better, a certificate) alongside the old one; both are valid at the same time. Set a lifetime under 12 months and put the expiry in the description so the next person can read it in the portal.
$new = Add-MgApplicationPassword -ApplicationId $objectId -PasswordCredential @{ DisplayName = "rotation 2026-09, expires 2027-03" EndDateTime = (Get-Date).AddMonths(6) } $new.SecretText # shown once; store it in your vault now $new.KeyId - Deploy the new secret to every workload you mapped in step 3: the app service settings, the pipeline variables, the on-premises scheduled task, the Key Vault secret the code reads.
- Confirm sign-ins on the new key ID. Re-run the step 3 query and check that every workload for this
AppIdnow reportsServicePrincipalCredentialKeyIdequal to$new.KeyId, and that the old key ID has gone quiet. Wait through at least one full cycle of the least frequent job. If anything still presents the old key ID, you missed a deployment target; fix that before continuing. - Remove the old credential.
Once removed, the secret cannot be brought back, because its value was only ever shown once. That is why step 3 comes before step 4.Remove-MgApplicationPassword -ApplicationId $objectId -KeyId $oldKeyId
Certificates follow the same order using keyCredentials; the sign-in log shows the thumbprint in use instead of the key ID.
Step 5: Stop it coming back
Cap lifetimes. Client secret lifetime is capped at 24 months and Microsoft recommends less than 12; make 12 the internal rule and audit against it with the step 1 export (Expires minus Created above 365 days).
Move production apps to certificates. Microsoft recommends a certificate rather than a secret before an app goes to production. Upload accepts .cer, .pem, or .crt files; for production use a CA-signed certificate managed in Azure Key Vault, and keep self-signed certificates for testing only. Certificates still expire, so they stay in the inventory, but they cannot be pasted into a chat message.
Move anything that can to federated identity credentials. Workload identity federation configures an app registration or a user-assigned managed identity to trust tokens from an external identity provider; the workload exchanges that token for a Microsoft identity platform access token, so no secret or certificate is stored or rotated. The admin center covers GitHub Actions, Kubernetes, customer managed keys, and any other OpenID Connect issuer. For workloads on Azure compute, a managed identity removes the secret outright, and a managed identity can itself be the federated credential on an app registration, which Microsoft calls the recommended approach whenever an Entra app is required. Two constraints: issuer, subject, and audience must match the incoming token case-sensitively, and tokens issued by Entra ID itself cannot be used in this flow. Microsoft's guide "Migrate applications away from secret-based authentication" covers the transition.
If a secret may have leaked
An expiring secret is a lifecycle problem. A secret that appeared in a repository, a ticket, or a chat is an incident, and the order changes. Microsoft's remediation steps for a compromised workload identity: inventory all credentials on both the service principal and application objects, add a new credential (x509 certificate recommended), remove the compromised credentials (all of them if the account is believed at risk), and rotate any Azure Key Vault secrets the service principal can reach. Deactivating the application is documented as a way to stop new token issuance while keeping both objects intact for investigation. If the application holds a privileged role, read what to do when a service principal holds Global Administrator first, because the blast radius is everything the app can reach, not the app itself.
Where Orbitra fits
Orbitra puts app registrations, service principals, their credentials, and their owners in the same graph as your users, roles, and Azure RBAC, with blast radius traversal across all of them. On every sync it flags credentials that are expiring, expired, or have never been rotated, and app registrations with no owner; roughly 40 percent of its detection rules target applications, service principals, consent grants, and app credentials. When a credential has to come off or a service principal has to be disabled, the action comes from an allowlisted catalog and is governed by tenant policy. In Recommend mode, your team executes it; in Approve mode, a named person signs off before Orbitra executes. Orbitra tells you which steps are permanent before you approve them, and independently re-reads Microsoft after supported response actions to verify the final state. Every customer tenant uses Recommend or Approve today. How Orbitra works.
Related reading
- Non-human identity: why applications, service principals, and managed identities need their own lifecycle.
- Service principal and app registration: which object holds what.
- Containment verification: re-reading Microsoft after a change.
- OAuth consent response: the other half of application risk.
- Back to the guides index.
Sources
- Add and manage application credentials in Microsoft Entra ID (credential types, secret lifetime cap and 12 month recommendation, value shown once, certificate guidance, federated credential scenarios), checked September 2026.
- Application and service principal objects in Microsoft Entra ID (application object versus service principal, home tenant behavior, deactivation), checked September 2026.
- Workload identity federation (problem statement, token exchange, issuer and subject matching, Entra tokens excluded), checked September 2026.
- What are managed identities for Azure resources? (managed identity replacing secrets, managed identity as a federated credential), checked September 2026.
- Securing workload identities with Microsoft Entra ID Protection (investigation checklist and remediation steps), checked September 2026.
- Sign-in logs in Microsoft Entra ID (four log types, Reports Reader, Usage and insights reports), checked September 2026.
- signIn resource type (Microsoft Graph beta) (servicePrincipalCredentialKeyId and servicePrincipalCredentialThumbprint), checked September 2026.
- List signIns (Microsoft Graph beta) (service principal sign-in filter and Get-MgBetaAuditLogSignIn), checked September 2026.
Frequently asked questions
How long can a client secret last in Entra ID?
Client secret lifetime is capped at 24 months; a custom lifetime longer than that cannot be set. Microsoft recommends an expiration of less than 12 months, and says client secrets should not be used in production environments at all.
Can I recover the value of an existing client secret?
No. The secret value is shown once, when it is created, and is never displayed again after you leave the page. If the value is lost, create a new secret and rotate to it; do not try to recover the old one.
How do I tell which client secret an application is actually using?
Read the service principal sign-in logs through the Microsoft Graph beta signIn resource. The beta property servicePrincipalCredentialKeyId is the key credential ID the service principal used to authenticate; servicePrincipalCredentialThumbprint is the certificate thumbprint. The v1.0 resource does not expose these properties.
Should I replace client secrets with certificates or federated credentials?
Microsoft recommends a certificate rather than a client secret before an application moves to production, and steers workloads such as GitHub Actions, Kubernetes, and compute outside Azure to federated identity credentials, which remove the stored secret entirely. Workloads on Azure compute can use a managed identity instead.
What should I do with an app registration that has no owner?
Assign at least two owners before you touch its credentials. Check the audit log and your deployment records for who last changed it, and check the service principal sign-in logs for what still uses it. If nobody claims it and nothing signs in with it, treat it as a removal candidate rather than a rotation candidate.