App registrations sign in with client secrets and certificates that are easy to create and easy to forget. In this lab you inventory every application credential with Microsoft Graph PowerShell, flag expired, soon to expire and long-lived secrets, find apps with no owner, rotate one credential safely and review options that reduce the need for secrets.
Key takeaways
- Application credentials do not use MFA, so a leaked secret is a working login
- Inventory both passwordCredentials and keyCredentials for every application
- Add the new credential and deploy it before you remove the old one
- Prefer managed identities, federated credentials or certificates over client secrets
Why this lab matters
Users get MFA, Conditional Access and sign-in risk checks. Applications mostly get a secret string and a long expiry date. Attackers know this, which is why stolen application credentials appear in so many cloud incidents. The first control is simple: know every credential you have, who owns it and when it expires.
What you need
- A test tenant with a few app registrations. Create two or three and add secrets with different expiry dates so you have data.
- The Microsoft Graph PowerShell SDK and the Application Administrator or Cloud Application Administrator role.
Step 1: Inventory every credential
Connect-MgGraph -Scopes "Application.Read.All"
$now = Get-Date
$report = foreach ($app in Get-MgApplication -All -Property Id, AppId, DisplayName, PasswordCredentials, KeyCredentials) {
$owners = (Get-MgApplicationOwner -ApplicationId $app.Id -All).Count
foreach ($c in @($app.PasswordCredentials) + @($app.KeyCredentials)) {
if (-not $c) { continue }
[pscustomobject]@{
App = $app.DisplayName
AppId = $app.AppId
Type = if ($c.PSObject.Properties.Name -contains 'Hint') { 'Secret' } else { 'Certificate' }
KeyId = $c.KeyId
Start = $c.StartDateTime
End = $c.EndDateTime
DaysLeft = [int]($c.EndDateTime - $now).TotalDays
LifetimeDays = [int]($c.EndDateTime - $c.StartDateTime).TotalDays
Owners = $owners
}
}
}
$report | Sort-Object DaysLeft | Format-Table App, Type, DaysLeft, LifetimeDays, Owners
$report | Export-Csv .\app-credentials.csv -NoTypeInformation
Step 2: Flag the risks
$report | Where-Object DaysLeft -lt 0 # expired, safe to remove once unused
$report | Where-Object { $_.DaysLeft -ge 0 -and $_.DaysLeft -le 30 } # outage risk
$report | Where-Object LifetimeDays -gt 365 # long-lived
$report | Where-Object Owners -eq 0 # nobody to ask
| Finding | Why it matters | Action |
|---|---|---|
| Expired | Clutter that hides real problems | Remove |
| Expires in 30 days | The app will stop working | Rotate now |
| Lifetime over one year | A leak stays useful for longer | Replace with a shorter one |
| No owner | No one can confirm whether it is needed | Find and assign two owners |
Step 3: Check whether a credential is still used
Before you remove anything, look at the service principal sign-in logs. In the Entra admin centre open Sign-in logs and choose the Service principal sign-ins tab, then filter by the application. If the logs are in Log Analytics you can see which credential was used:
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| summarize LastUsed = max(TimeGenerated), SignIns = count()
by AppId, ServicePrincipalName, ServicePrincipalCredentialKeyId
Step 4: Rotate one secret safely
- Add a new secret with a short lifetime:
Connect-MgGraph -Scopes "Application.ReadWrite.All" $app = Get-MgApplication -Filter "displayName eq 'lab-reporting-app'" $new = Add-MgApplicationPassword -ApplicationId $app.Id -PasswordCredential @{ displayName = "rotated-2026-11" endDateTime = (Get-Date).AddDays(90) } $new.SecretText # shown once. Store it in a vault, not in a script - Update the application or pipeline that uses it and confirm it signs in with the new credential.
- Remove the old one by its key ID:
Remove-MgApplicationPassword -ApplicationId $app.Id -KeyId "<old KeyId from the report>"
Never paste a secret into a ticket, chat or source repository. If one has been exposed, treat it as compromised and rotate it straight away.
Step 5: Reduce the number of secrets
- Managed identities for workloads that run in Azure. There is no credential for you to store or rotate.
- Federated identity credentials for GitHub Actions, Kubernetes and other platforms that can present their own token.
- Certificates where a credential is unavoidable, with the private key held in a vault or hardware module.
- Application management policies in Entra, which can block new secrets or cap their lifetime across the tenant. These need Workload Identities Premium licences for some restrictions, so check the current licensing before you plan on them.
Run the Step 1 script again. The lab is complete when there are no expired credentials, nothing expires within 30 days, every app has at least one owner and your rotated app still signs in.
Evidence to keep
- The before and after CSV exports.
- The list of owners you assigned.
- A sign-in log entry showing the rotated app authenticating with the new key ID.
Clean up
Delete the test app registrations and any stored test secrets.
Frequently asked questions
How do I find expiring client secrets in Entra ID?
Use Microsoft Graph PowerShell to read passwordCredentials and keyCredentials from every application with Get-MgApplication, then compare each endDateTime with today's date. The script in this lab exports the result to CSV.
Is a client secret or a certificate more secure for an app registration?
A certificate is generally safer because the private key can stay inside a vault or hardware module and is never sent to Entra ID. A client secret is a shared string that is easy to copy and leak. A managed identity or federated credential is better than both where it is available.
How long should an app registration secret last?
Keep lifetimes as short as your rotation process allows. Many teams use 90 to 180 days. Anything longer than a year should be replaced, and an application management policy can enforce a maximum.
What happens if I delete a secret that is still in use?
The application will fail to authenticate until it is given a valid credential. Check service principal sign-in logs first, and always add and deploy the new credential before removing the old one.