AADSTS7000215: Invalid client secret is provided
Short answer: AADSTS7000215 means an app or service principal sent a client secret that Microsoft Entra ID didn't accept. Usually it is a developer or rotation mistake. The security signal is repeated failures for one app ID from an IP you don't own, which suggests someone trying a leaked or old secret. Hunt it in AADServicePrincipalSignInLogs.
| Code | AADSTS7000215 |
|---|---|
| Microsoft name | Invalid client secret |
| Category | App configuration |
| Type | Failure |
| Attack signal | Medium: worth a look in context |
| MITRE ATT&CK | T1078.004 Valid Accounts: Cloud Accounts |
| Where to look | AADServicePrincipalSignInLogs |
| Microsoft lookup | login.microsoftonline.com/error?code=7000215 |
What does AADSTS7000215 mean?
Section titled “What does AADSTS7000215 mean?”Invalid client secret is provided. Developer error - the app is attempting to sign in without the necessary or correct authentication parameters.
The reference table gives this code no PascalCase name. Two siblings help you tell the cases apart:
- 7000222 InvalidClientSecretExpiredKeysProvided: “The provided client secret keys are expired. Create new keys for your app, or consider using certificate credentials for added security”
- 7000218: “The request body must contain the following parameter: ‘client_assertion’ or ‘client_secret’.”
So 7000222 means the secret was right but expired, 7000218 means no secret or assertion was sent at all, and 7000215 means a secret was sent and it was wrong.
Is it an attack?
Section titled “Is it an attack?”Usually not. Microsoft labels it a developer error, and most 7000215 comes from a typo in configuration or a system that still holds a secret you rotated. The security reading depends on where the requests come from.
| Pattern | Likely meaning |
|---|---|
| 7000215 for one app from your own servers or build agents, starting right after a secret rotation | A system still holding the old secret. Benign, but it tells you exactly which systems to update. |
| 7000215 for one app from an IP you don’t own and have never seen succeed for that app | Someone trying a secret for your app from outside. A leaked or old secret is the likely source. Investigate. |
7000215 followed by a success (ResultType "0") for the same app from the same unknown IP |
The attacker found a working secret. Treat the app as compromised. |
| 7000215 spread across many app IDs from one IP | Someone testing a set of credentials against your tenant. Investigate the source. |
How do I find it in my logs (KQL)?
Section titled “How do I find it in my logs (KQL)?”Failures by app and source IP:
AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "7000215"| summarize Attempts = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), KeyIds = make_set(ServicePrincipalCredentialKeyId, 10) by AppId, ServicePrincipalName, IPAddress| sort by Attempts descFailures 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(1d)| where ResultType == "7000215"| join kind=leftanti knownAppIPs on AppId, IPAddress| summarize Attempts = count(), LastSeen = max(TimeGenerated) by AppId, ServicePrincipalName, IPAddress| sort by Attempts descAny IP from that list that later succeeds for the same app:
let failedFrom = AADServicePrincipalSignInLogs | where TimeGenerated > ago(7d) | where ResultType == "7000215" | summarize FirstFail = min(TimeGenerated) by AppId, IPAddress;AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "0"| join kind=inner failedFrom on AppId, IPAddress| where TimeGenerated > FirstFail| project TimeGenerated, AppId, ServicePrincipalName, IPAddress, ServicePrincipalCredentialKeyId, FirstFail| sort by TimeGenerated ascCheck 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"7000215"and that successes carry"0". If they don’t, filter onResultSignature(“Contains the error code, if any, for the sign-in operation”) instead. - Whether
ServicePrincipalCredentialKeyIdis filled in on a failed secret isn’t documented. A wrong secret may not match any key, so the column may be empty. On successes it tells you which credential was used.
How does Conditional Access affect it?
Section titled “How does Conditional Access affect it?”- A service principal using a client secret has no MFA step.
- 7000215 is a failed credential check. For user sign-ins, Conditional Access only evaluates after the first factor succeeds; the same logic suggests no policy was evaluated here. That is reasoning from the user case, not a documented statement for service principals.
- So the controls that matter are on the credential itself: who can read it, where it is stored, and whether it is a secret at all.
How do I fix AADSTS7000215?
Section titled “How do I fix AADSTS7000215?”For developers: check that the app sends the current secret for the right app registration and tenant. If the secret expired, you get 7000222, not 7000215.
For admins:
- After a rotation, use the first query to list every IP still failing for that app, and update those systems.
- If failures come from an IP you don’t own, treat the app’s existing secrets as exposed: create a new credential, remove the old ones from the app registration, and update your own systems.
- If the third query returns a row, the app was used by someone else. Review what the app’s permissions allow and what it accessed after that time.
- Follow Microsoft’s own hint in the 7000222 message and “consider using certificate credentials for added security”.
Related codes
- AADSTS700016
- AADSTS500011
- AADSTS65001