AADSTS7000222: The provided client secret keys are expired
Short answer: AADSTS7000222 means an app or service principal sent a client secret that has passed its expiry date. It is almost always a missed rotation, not an attack. The security angle is secret hygiene: an expired secret still sitting in config files shows where secrets live. Microsoft recommends managed identities or certificates instead. Hunt it in AADServicePrincipalSignInLogs.
Fix: Developer: create a new credential on the app registration's Certificates and secrets page and update every system that uses it. Admin: find every IP still sending the expired secret, update those systems, then remove the expired secret. Move to a managed identity or certificate where you can. Full steps
| Code | AADSTS7000222 |
|---|---|
| Microsoft name | InvalidClientSecretExpiredKeysProvided |
| Category | App configuration |
| Type | Failure |
| Attack signal | Low: usually configuration or user error |
| Where to look | AADServicePrincipalSignInLogs |
| Microsoft lookup | login.microsoftonline.com/error?code=7000222 |
What does AADSTS7000222 mean?
Section titled “What does AADSTS7000222 mean?”Microsoft’s description:
InvalidClientSecretExpiredKeysProvided: The provided client secret keys are expired. Create new keys for your app, or consider using certificate credentials for added security
An app tried to get a token with a client secret that was valid once but has passed its end date. The secret itself was right. Nobody replaced it in time.
Two siblings help you tell the cases apart:
- 7000215: “Invalid client secret is provided.” A secret was sent and it was wrong.
- 7000218: “The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’.” No secret or assertion was sent at all.
Is it an attack?
Section titled “Is it an attack?”Almost never. 7000222 is what a missed rotation looks like: a script, scheduled job or integration still holds a secret that expired. It usually shows up as a sudden outage of that integration.
| Pattern | Likely meaning |
|---|---|
| 7000222 for one app from your own servers, starting on a specific day | The secret expired that day and nobody rotated it. Benign, but the integration is broken. |
| 7000222 from your own servers after you created a new secret | You added a new secret but missed one system that still holds the old one. |
| 7000222 for an app from an IP you don’t own | Someone outside holds a copy of an old secret for your app. Our interpretation, not a Microsoft statement: the expiry stopped them, but the secret leaked at some point. Find out where it was stored. |
The useful security reading is the third row and the inventory it gives you. Every IP that sends an expired secret is a place where that secret was stored. If one of those systems was compromised, read hunting credential reuse after a web-server breach for which other secrets to rotate.
How do I find it in my logs (KQL)?
Section titled “How do I find it in my logs (KQL)?”Which apps are failing, and from where:
AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "7000222"| summarize Attempts = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), KeyIds = make_set(ServicePrincipalCredentialKeyId, 10) by AppId, ServicePrincipalName, IPAddress| sort by Attempts descExpired-secret attempts from IPs that never had a success for the same app in the last 30 days:
let knownAppIPs = AADServicePrincipalSignInLogs | where TimeGenerated > ago(30d) | where ResultType == "0" | distinct AppId, IPAddress;AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "7000222"| join kind=leftanti knownAppIPs on AppId, IPAddress| summarize Attempts = count(), LastSeen = max(TimeGenerated) by AppId, ServicePrincipalName, IPAddress| sort by Attempts descCheck two things in your own workspace before relying on these queries:
- The Azure Monitor reference for
AADServicePrincipalSignInLogsdescribesResultTypeas “Success or Failure”, while the user sign-in tables document it as the numeric error code as a string. Look at a few failed rows and confirm they carry"7000222"and that successes carry"0". If they don’t, filter onResultSignatureinstead. - Whether
ServicePrincipalCredentialKeyIdis filled in on an expired-secret failure isn’t documented. If it is, it tells you exactly which secret is still in use, which makes cleanup easy.
A secret used by a confidential client in a delegated (user) flow may surface in the user sign-in tables instead. That is our reading of how the flows work, not a documented statement. If the first query returns nothing but users report the error, search SigninLogs for the same ResultType.
How does Conditional Access affect it?
Section titled “How does Conditional Access affect it?”- 7000222 is a failed credential check, so no policy grants or blocks it. Conditional Access for workload identities can’t fix an expired secret.
- Microsoft notes that workload identities “Can’t perform multifactor authentication” and “Need to store their credentials or secrets somewhere.” The control that matters is the credential type.
- Conditional Access for workload identities can add location and risk conditions to single-tenant service principals, which limits where a leaked secret works. It needs Workload Identities Premium licenses.
How do I fix AADSTS7000222?
Section titled “How do I fix AADSTS7000222?”For developers: create a new credential on the app registration’s Certificates and secrets page and update every system that uses it. Don’t stop at the first system that works again.
For admins:
- Use the first query to list every IP still sending the expired secret, and update or retire each of those systems.
- Remove the expired secret from the app registration once nothing uses it. Microsoft: “An unused credential on an application can result in a security breach.”
- Move away from secrets. Microsoft’s order of preference is a managed identity first, then certificate credentials: “Don’t use password credentials, also known as secrets.” Certificate credentials work “anywhere a client secret could be used”, including the client credentials flow.
- Use application management policies to limit secret lifetimes or block new secrets, so the next rotation is planned rather than an outage.
- If the second query returns an IP you don’t own, treat every credential that was stored alongside that secret as exposed.
Related codes
- AADSTS7000215: Invalid client secret is provided
- AADSTS700016: Application not found in the directory
- AADSTS500011: The resource principal was not found in the tenant
- AADSTS70002: error validating client credentials
- AADSTS700024: Client assertion is not within its valid time range
- AADSTS5002730: Unsupported key for the JWT signing algorithm