Symptoms
AADSTS7000222: The provided client secret keys for app '<id>' are expired.AADSTS7000215: Invalid client secret provided(wrong value after a partial rotation).- Azure DevOps service connections, ADF linked services, Terraform and scripts all fail at once.
1. Find the app and its credentials
az ad app show --id <appId> --query "{name:displayName, secrets:passwordCredentials[].{hint:hint, end:endDateTime, id:keyId}, certs:keyCredentials[].{end:endDateTime, id:keyId}}" -o json
Find which callers used it recently:
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(1d)
| where AppId == "<appId>"
| summarize count() by ResultType, IPAddress, ResourceDisplayName
2. Add a new secret without removing the old one
az ad app credential reset --id <appId> --append --display-name "rotated-2026-10-24" --years 1 --query password -o tsv
--append keeps existing credentials. Without it, the command removes them, which breaks anything that still uses a valid older secret.
Warning
The secret value is shown once. Put it straight into Key Vault, not a chat window or ticket.
az keyvault secret set --vault-name kv-platform --name sp-deploy-secret --value "<new-secret>"
3. Update every consumer
| Consumer | Where to update |
|---|---|
| Azure DevOps service connection | Project settings → Service connections → Edit → secret |
| GitHub Actions | Repository or environment secret AZURE_CLIENT_SECRET |
| ADF / Synapse linked service | Prefer a Key Vault reference; otherwise edit and publish |
| App Service / Functions | App setting or Key Vault reference, then restart |
| Terraform | ARM_CLIENT_SECRET in the pipeline variable group |
Test one consumer end to end before updating the rest.
4. Remove the expired credential
az ad app credential delete --id <appId> --key-id <old-keyId>
5. Find what expires next
az ad app list --all --query "[].{name:displayName, appId:appId, ends:passwordCredentials[].endDateTime}" -o json > apps.json
$apps = Get-Content apps.json | ConvertFrom-Json
$apps | ForEach-Object {
foreach ($e in $_.ends) { if ([datetime]$e -lt (Get-Date).AddDays(30)) { "{0} {1} {2}" -f $_.name, $_.appId, $e } }
}
Prevent it next time
- Move Azure-hosted workloads to managed identities — no secret to expire.
- Use workload identity federation for GitHub Actions, Azure DevOps and Terraform Cloud.
- Where secrets remain, alert 30 days before expiry and keep values only in Key Vault.
- Record an owner for every app registration.