Forum Discussion
An Entra app credential is expiring. Which workload still uses it?
A credential rotation ticket often starts with three things: an application name, an expiry date and a deadline. That is enough to know something must happen, but not enough to rotate safely.
Before touching the credential, I want to know whether that exact key is still requesting tokens, which resources it accesses and where those requests appear to come from. If Microsoft Entra sign-in logs are already connected to Log Analytics or Microsoft Sentinel, KQL can answer most of that without changing anything in the tenant.
This is the workflow I use.
What needs to be in the workspace
The important diagnostic category is ServicePrincipalSignInLogs. In Log Analytics it becomes the AADServicePrincipalSignInLogs table. Normal SigninLogs are for interactive user sign-ins and are not the right data source for an app authenticating with its own secret or certificate.
The diagnostic setting is configured under:
Microsoft Entra ID > Monitoring & health > Diagnostic settings
Make sure ServicePrincipalSignInLogs is being sent to the workspace you are querying. Microsoft documents the available log categories and their purpose here:
https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-diagnostic-settings-logs-options
From the Entra recommendation or the affected application, copy the Application (client) ID. Do not confuse it with the application object ID or the service principal object ID. In the Kusto table:
- AppId is the Application (client) ID.
- ServicePrincipalId is the object ID of the service principal in the tenant.
- ServicePrincipalCredentialKeyId identifies the secret or certificate key used for the token request.
- ServicePrincipalCredentialThumbprint can help identify a certificate.
- ResourceDisplayName and ResourceServicePrincipalId identify the target resource.
Those fields are part of the documented AADServicePrincipalSignInLogs schema:
https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/aadserviceprincipalsigninlogs
Step 1: See every credential recently used by the application
Replace TargetAppId with the Application (client) ID from Entra:
let Lookback = 30d;
let TargetAppId = "00000000-0000-0000-0000-000000000000";
AADServicePrincipalSignInLogs
| where TimeGenerated >= ago(Lookback)
| extend SignInTime = coalesce(CreatedDateTime, TimeGenerated)
| where SignInTime >= ago(Lookback)
| where AppId =~ TargetAppId
| extend SignInSucceeded = tostring(ResultType) == "0"
| summarize
FirstSeen = min(SignInTime),
LastSeen = max(SignInTime),
Attempts = count(),
SuccessfulSignIns = countif(SignInSucceeded),
FailedSignIns = countif(SignInSucceeded == false),
CredentialTypes = make_set(ClientCredentialType, 10),
SourceIPs = make_set(IPAddress, 20),
UserAgents = make_set(UserAgent, 10),
TargetResources = make_set(
strcat(ResourceDisplayName, " [", ResourceServicePrincipalId, "]"),
20
)
by AppId,
ServicePrincipalId,
ServicePrincipalName,
ServicePrincipalCredentialKeyId,
ServicePrincipalCredentialThumbprint,
FederatedCredentialId
| order by LastSeen descThis gives me a short usage map for the application. If the expiring credential's key ID appears in the result, that exact credential was used for at least one recorded token request during the selected lookback period. The target resources show what it requested a token for. Source IPs and user agents usually help narrow down the workload.
They are clues, not host identification. A source IP might be a NAT gateway, proxy or shared outbound address, and user-agent strings are neither guaranteed nor trustworthy. I use them to narrow the search in deployment settings, automation jobs, Key Vault references and CI/CD configuration, not as final proof of where the workload runs.
Rows with a populated FederatedCredentialId represent workload identity federation. They should not be mistaken for use of the expiring secret or certificate.
Step 2: Inspect the exact credential
Once the expiring key ID is known, this query shows the individual sign-in records for that key:
let Lookback = 30d;
let TargetAppId = "00000000-0000-0000-0000-000000000000";
let ExpiringCredentialKeyId = "11111111-1111-1111-1111-111111111111";
AADServicePrincipalSignInLogs
| where TimeGenerated >= ago(Lookback)
| extend SignInTime = coalesce(CreatedDateTime, TimeGenerated)
| where SignInTime >= ago(Lookback)
| where AppId =~ TargetAppId
| where ServicePrincipalCredentialKeyId =~ ExpiringCredentialKeyId
| extend Result = iff(tostring(ResultType) == "0", "Success", "Failure")
| project
SignInTime,
Result,
ResultType,
ResultDescription,
ServicePrincipalName,
ServicePrincipalId,
ClientCredentialType,
ServicePrincipalCredentialKeyId,
ServicePrincipalCredentialThumbprint,
IPAddress,
UserAgent,
ResourceDisplayName,
ResourceServicePrincipalId,
CorrelationId
| order by SignInTime descA successful row is direct evidence that Entra accepted that credential for a token request at that time. Repeated failures can also be useful: they might show an incomplete rotation, an expired credential that is still configured somewhere, or a forgotten workload that keeps retrying.
No rows do not mean "safe to delete." They only mean that no matching event exists in the data currently available to this workspace for the selected period. The workload might run monthly, the event might be outside retention, the diagnostic category might have been enabled recently, or the relevant logs might be routed elsewhere.
The table also does not contain the application's business owner. I confirm ownership on the application or enterprise application in Entra, or enrich the result from an existing CMDB or Sentinel watchlist. I would not invent an owner from the last source IP or from whoever originally created the app.
Step 3: Prove the replacement is actually in use
After adding the replacement credential and updating the workload, I use the cutover time plus the old and new key IDs to verify the change:
let CutoverTime = datetime(2026-08-26T12:00:00Z);
let TargetAppId = "00000000-0000-0000-0000-000000000000";
let OldCredentialKeyId = "11111111-1111-1111-1111-111111111111";
let NewCredentialKeyId = "22222222-2222-2222-2222-222222222222";
AADServicePrincipalSignInLogs
| where TimeGenerated >= CutoverTime
| extend SignInTime = coalesce(CreatedDateTime, TimeGenerated)
| where SignInTime >= CutoverTime
| where AppId =~ TargetAppId
| where ServicePrincipalCredentialKeyId in~ (OldCredentialKeyId, NewCredentialKeyId)
| extend Credential = case(
ServicePrincipalCredentialKeyId =~ OldCredentialKeyId, "old",
ServicePrincipalCredentialKeyId =~ NewCredentialKeyId, "new",
"other"
)
| where Credential != "other"
| summarize
FirstSeen = min(SignInTime),
LastSeen = max(SignInTime),
Attempts = count(),
SuccessfulSignIns = countif(tostring(ResultType) == "0"),
FailedSignIns = countif(tostring(ResultType) != "0"),
SourceIPs = make_set(IPAddress, 20),
TargetResources = make_set(ResourceDisplayName, 20)
by Credential, ServicePrincipalCredentialKeyId
| order by LastSeen descBefore I remove the old credential, I want to see a successful token request with the new key ID after the cutover. I also run a real operation against the intended resource. A generic process health check is not enough if it never exercises the dependency that uses Entra authentication.
Continued sign-ins with the old key after the cutover are a clear sign that another instance, deployment slot or workload still has the old value. An absence of old-key sign-ins becomes meaningful only after the expected workload schedule has been covered and the logs have arrived in the workspace.
One final trap is token caching. Service principal sign-in logs show token requests, not every API call made with a token. Removing the old client credential prevents it from obtaining new tokens, but an access token already issued by Microsoft Entra normally remains valid until it expires. The rotation test must therefore force the workload to acquire a fresh token.
Access token documentation:
https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens
My rotation order is simple:
- Identify the exact old credential and the workload using it.
- Add the replacement while the old credential is still valid.
- Deploy the new credential to the workload's secret store.
- Force a fresh token request and execute a real operation.
- Confirm the new key ID in AADServicePrincipalSignInLogs.
- Watch for unexpected use of the old key for an appropriate period.
- Remove the old credential after the evidence and rollback window are sufficient for that workload.
Microsoft Entra's preview recommendations for expiring application credentials and expiring service principal credentials are useful starting points. For me, the KQL correlation is the part that turns the warning into a controlled change instead of a blind rotation.
Recommendation documentation:
https://learn.microsoft.com/en-us/entra/identity/monitoring-health/recommendation-renew-expiring-application-credential
https://learn.microsoft.com/en-us/entra/identity/monitoring-health/recommendation-renew-expiring-service-principal-credential
How are you carrying application ownership into Sentinel? A maintained watchlist works, but it can easily become just another stale inventory.