AADSTS7000222: The provided client secret keys for app '<your-app-id>' are expired.
The fix itself is quick. The harder part is why it happened at all. Azure knew this secret would expire, and it never told you. This guide covers the immediate fix, the reasons it can look like the fix did not work, and how to make sure you are never surprised by an expired secret again.
What AADSTS7000222 actually means
AADSTS7000222 is Microsoft Entra ID (formerly Azure AD) telling you that the
client secret your application used to authenticate has passed its expiration
date. The token request was rejected with invalid_client, because the
credential it presented is no longer valid.
Two things are worth understanding up front:
- The app registration itself did not expire. App registrations do not expire. Only their credentials — client secrets and certificates — have expiration dates. The registration is fine. Only the secret attached to it is dead.
- There is no “renew” button. An expired secret cannot be extended or reactivated. You create a new secret, update every place the old one is referenced, and remove the old one. That is the entire fix.
This is different from AADSTS7000215: Invalid client secret provided, which
usually means the secret value is wrong: a typo, a trailing space, the Secret ID pasted
instead of the value, or the wrong tenant. If you are seeing 7000215, check
that you copied the right value before you assume it expired.
How to fix it
1. Find the right app registration
Open the Microsoft Entra admin center and go to Identity → Applications → App registrations. Search for the app by name, or by the Application (client) ID shown in the error.
If you cannot find it under App registrations, look under Enterprise applications instead (more on that below).
2. Confirm the secret is expired
Open the app and go to Certificates & secrets → Client secrets. You will see each secret with an Expires date, and the expired one will be flagged. Note the Description and Secret ID of the expired secret, so you know which one expired and can match it to where it is used.
3. Create a new client secret
Click + New client secret, give it a clear description (for example,
prod-api-2026, so you can recognize it later), and choose an expiry.
A few things to know about the expiry:
- The Azure portal caps client secret expiry at 24 months. The “never expires” option was removed years ago, for security reasons.
- Microsoft's own recommendation is 6 months for client secrets. Shorter is safer, but only if you have alerting in place to rotate them in time (see the prevention section).
- You can create longer-lived secrets through the Azure CLI (
az ad app credential reset) or Microsoft Graph, but that trades security for convenience, and is not recommended for production.
Click Add, then copy the Value immediately. This is the only time the secret value is ever shown. Once you navigate away, it is gone, and you have to create another one. This is also why a monitoring tool can never read your secret values: the Graph API does not return them after creation.
4. Update everywhere the secret is used
The new secret does nothing until the application actually uses it. The old, expired value is still sitting in one or more of these places:
- App Service / Function App — Configuration → Application settings (often an
AzureAd:ClientSecretor similar key). This one is commonly missed, because it is buried in the configuration blade. - Azure Key Vault — if your app pulls the secret from Key Vault, update the Key Vault secret's value, not the app config.
- Azure DevOps / GitHub Actions service connections — classic service connections that authenticate with an app registration + secret will break the moment the secret expires. Update the service connection's credentials.
- Logic Apps, Automation runbooks, Data Factory linked services — any of these that authenticate as the app registration.
- Application config files, environment variables, container secrets, CI/CD variable groups — anywhere a developer hard-wired the value.
- External / partner systems — if a third party authenticates as this app, they need the new value too.
Paste the new value, save, and redeploy or restart the affected service so it picks up the change.
5. Verify and clean up
Trigger the workflow that failed: re-run the pipeline, call the API, start the job again. Once it succeeds with the new secret, go back to Certificates & secrets and delete the expired secret, so it cannot cause confusion later.
Common reasons the fix looks like it did not work
“It still says expired even though I have an active secret”
Almost always, the application is still pointing at the old secret. An app registration can hold several secrets at the same time, and adding a new one does not change which one your app sends. Make sure the config, Key Vault, or service connection actually references the new value, then restart the service.
“The app isn't under App registrations — only Enterprise applications”
If the app shows up under Enterprise applications with an empty Owners list, and nothing appears under App registrations, the registration most likely lives in another tenant. This is common with third-party SaaS, marketplace apps, or apps created by someone who has since left the company. You cannot rotate a secret on a registration you do not own. You will need the owning tenant's admin, or the vendor, to issue a new credential.
“Unable to save — the credential limit has been reached”
If you cannot add a new secret, check the app's Supported account types. Apps set to personal Microsoft accounts and organizational are limited to two secrets. Delete an old or expired one to make room.
“I rotated it but the pipeline still fails”
For Azure DevOps in particular, the durable answer is not a new secret at all. Switch the service connection to Workload Identity Federation (OIDC), so it receives a short-lived token and never stores a secret that can expire. More on that below.
Find out which other secrets are about to expire
You just fixed one secret. How many others are close to expiring right now? Here is a Microsoft Graph PowerShell snippet that lists every client secret in your tenant, sorted by how soon it expires:
Connect-MgGraph -Scopes "Application.Read.All"
$apps = Get-MgApplication -All
$results = foreach ($app in $apps) {
foreach ($secret in $app.PasswordCredentials) {
[pscustomobject]@{
AppName = $app.DisplayName
AppId = $app.AppId
KeyId = $secret.KeyId
Expires = $secret.EndDateTime
DaysLeft = [math]::Round(($secret.EndDateTime - (Get-Date)).TotalDays)
}
}
}
$results | Sort-Object DaysLeft | Format-Table
Swap PasswordCredentials for KeyCredentials to do the same for
certificates. Run it, and you will almost certainly find a few secrets expiring in the
next 30–60 days that nobody is watching.
That script is useful, but notice what it is not. It only tells you the truth at the moment you run it. To turn it into real protection, you would have to schedule it on an Automation account or a Logic App, wire up email or webhook delivery, handle authentication and permissions, format the output into something readable, and keep all of it working as your tenant grows. This is the fragile, hand-built monitoring that every team eventually reinvents.
The real problem: Azure never warned you
Here is the part worth pausing on. The Azure portal shows you every secret's expiration date. It simply does not tell you about it. There is:
- no built-in expiration notification,
- no centralized view across all your app registrations,
- no alert into your email or incident channel.
So the first person to notice an expired secret is almost always a customer, or an engineer in the middle of an incident. The expiration date was visible the whole time. It was simply on a page that nobody had open.
In a small tenant, a scheduled PowerShell script may be enough. But a mid-sized Azure tenant easily runs 50 or more secrets across dozens of app registrations, each with its own expiration date. Tracking every one of them by hand, forever, is not a realistic operating model.
How to make sure this never surprises you again
Rotating the secret fixes this incident. Monitoring with alerts fixes the category of incident. With alerts in place, an expiration becomes a quiet calendar item instead of an outage.
A few things that matter to engineers specifically:
- It never sees your secret values. Graph does not return secret values after creation, so there is nothing sensitive to store. Token Watch reads only metadata: names, key IDs, and expiration dates.
- Read-only, always. It cannot modify your tenant, create credentials, or delete anything.
- Setup takes about a minute. Sign in, grant the read-only consent, and you immediately see every expiration status across the tenant. No agents, scripts, or installations.
- Alerts go where you already work: email, or a webhook into Slack, Teams, PagerDuty, ServiceNow, or whatever incident tool you already use.
The durable fix: stop using long-lived secrets at all
Monitoring solves the problem of being surprised. For new and high-value workloads, the deeper fix is to use credentials that do not expire on a manual schedule:
- Managed identities — for Azure resources (App Service, Functions, VMs, and so on) calling other Azure or Microsoft services. Azure manages the credential lifecycle for you, so there is no secret to rotate.
- Workload Identity Federation (OIDC) — for CI/CD (GitHub Actions, Azure DevOps) and external workloads. The platform exchanges a short-lived token, so there is no stored secret to expire.
- Certificates — if you must use app-registration credentials, certificates are generally preferred over secrets, though they expire too and still need monitoring.
The honest reality is that most tenants cannot remove client secrets overnight. There are always legacy apps, third-party integrations, and edge cases that still need them. For everything you can migrate, migrate. For everything you cannot, monitor it, so an expiration never turns into an incident again.
FAQ
7000222 specifically means the secret is expired. 7000215 means the secret is invalid — often a typo, a trailing space, the Secret ID pasted instead of the value, or the wrong tenant. Check your copied value before assuming expiration.
PasswordCredentials and KeyCredentials (script above), or use a monitoring service like Token Watch that surfaces every expiration date and alerts you before they lapse.
Token Watch