Security Advisories

AADSTS7000215 followed by a successful sign-in: how to check Entra service principal sign-ins for stolen app secrets

Cracked red app-secret key shattering against cyan firewall planes in front of a protected cloud tenant

Most admins read AADSTS7000215 in a service principal sign-in as a developer problem: someone pasted the wrong client secret into a pipeline, and it'll sort itself out at the next deploy. On its own, that's usually true. The version that matters is a 7000215 or 7000222 failure, followed by a success for the same app from an IP address you don't own.

Here is where we look for that pattern in the tenants we assess, how we contain it, and what keeps it from coming back.

Where AADSTS7000215 shows up: the Service principal sign-ins tab

Microsoft documents AADSTS7000215 as "Invalid client secret is provided" and AADSTS7000222 as "The provided client secret keys are expired." Both mean something presented a secret for your app ID to Entra. Neither appears on the default interactive sign-in view, because no user is involved.

Go to Entra admin center > Entra ID > Monitoring & health > Sign-in logs > Service principal sign-ins. Rows are aggregated by service principal, status, IP address and resource, so expand a row before you assume it's a single event. Filter Status to Failure, read the error code, then filter the same app to Success and compare IP addresses.

If you have Defender advanced hunting, this pulls apps that failed on a secret and also succeeded from the same IP:

EntraIdSpnSignInEvents
| where Timestamp > ago(30d)
| where IsManagedIdentity == false
| summarize SecretFailures = countif(ErrorCode in (7000215, 7000222)),
            Successes = countif(ErrorCode == 0),
            FirstFailure = minif(Timestamp, ErrorCode in (7000215, 7000222)),
            FirstSuccess = minif(Timestamp, ErrorCode == 0)
    by ServicePrincipalName, ApplicationId, IPAddress
| where SecretFailures > 0 and Successes > 0
| order by FirstFailure asc

Drop IPAddress from the by clause to catch a failure from one address and a success from another.

What breaks it:

  • Know your app's normal IPs before an incident. If nobody can say where a pipeline or connector runs from, you can't call a success "unknown."
  • Stream service principal sign-ins to Log Analytics (the AADServicePrincipalSignInLogs table) if you need more history than the portal keeps.
  • Separate config noise from hunting. One app failing from your own build agent is a ticket. The same app failing and then succeeding from a hosting provider is an incident.

The pattern that matters: failed secret, then success from an IP you don't own

In its November 5 write-up, Microsoft says it saw successful service principal sign-ins from 199.19.77[.]84 starting October 28, 2026, across multiple app IDs. In some cases the compromised credential belonged to a vendor's multitenant app and was used against downstream tenants. Microsoft's own guidance is the line to remember: 7000215 or 7000222 means someone holds a credential for your app, and a failure followed by a success is urgent.

The failures are the attacker working through a list of secrets, some stale and some current. The success is the one that still works. After that, the app has whatever API permissions and Azure role assignments you gave it, with no MFA in the way, because workload identities can't do MFA.

What breaks it:

  • Search service principal and non-interactive sign-in logs for the published IPs, including failed attempts, as far back as retention allows.
  • Check which credential signed in. The sign-in record carries the credential key ID, so you can tell whether the success used a secret you created or one somebody added.
  • Look at behavior as well as the IP. Microsoft flags one address authenticating to several service principals, or one app suddenly requesting tokens for Resource Manager, Key Vault, Storage and Graph in a short window.

Containment in order: block the sign-in, pull the credentials, rotate what it could read

Start with a Conditional Access policy for workload identities: Users or workload identities > Workload identities > select the service principals directly, target All resources, include any location and exclude your known ranges, then Block (the only grant available). Run it in report-only first if the app is production-critical. A blocked attempt then logs "Access has been blocked due to Conditional Access policies."

Read Microsoft's limits before you rely on it. Creating or changing these policies needs Workload Identities Premium. They apply only to single-tenant service principals registered in your tenant, so Microsoft apps, third-party SaaS apps and multitenant apps aren't covered, and neither are managed identities. Adding a service principal to a group in a policy doesn't work; it has to be assigned directly. Microsoft also recommends a user-scoped block on the same IP to cover replayed sessions.

What breaks it:

  • Remove every secret, certificate and federated credential on the affected app, including ones you didn't create. Tokens already issued stay valid until they expire.
  • Rotate everything in any Key Vault the app could read, plus credentials stored next to the leaked secret in the same repo or config.
  • MSPs: review Partner Center access, GDAP relationships and customer tenants, and notify affected customers under your agreements.

What "good" looks like: the five-item baseline

If you do nothing else this quarter, these five moves close the gap in order of impact:

  1. An owner on every app registration. An app with no owner is one nobody will notice failing on a secret.
  2. Certificates or federated credentials instead of client secrets. Application management policies can block new secrets for apps created after a set date and cap certificate lifetime; Microsoft recommends 180 days.
  3. Workload-identity Conditional Access on your privileged single-tenant apps, where you have the licensing. Start with apps that hold Graph application permissions or Key Vault access.
  4. An alert on 7000215 or 7000222 followed by a success for the same app, using the query above.
  5. Secrets out of code. GitHub secret scanning and push protection, because removing a secret from a commit doesn't revoke it.

If your sign-in logs show an app failing with AADSTS7000215 and then succeeding from an IP you don't own, rotating that one secret isn't the fix; our Microsoft 365 Security Hardening review inventories every app registration's secrets, certificates, and federated credentials, and puts workload-identity Conditional Access in front of the ones that matter.

The uncomfortable question

The question after a 7000215 isn't "who typed the wrong secret?" It's "who else has a secret for this app that still works, and where are they signing in from?" Open Service principal sign-ins, filter to Failure, sort by app, and then look at that app's successes for the same week.

Want to know which app secrets in your tenant still work?

The Security Hardening review inventories every app registration's secrets, certificates and federated credentials, then puts workload-identity Conditional Access in front of the ones that matter.

See M365 Security Hardening →

Keep reading