Troubleshooting Authentication Errors in Azure
Azure authentication errors appear as AADSTS error codes in browser redirects, as HTTP 401 responses in API calls, and as token acquisition exceptions in application logs. This page explains the most common error codes, how to diagnose managed identity failures from inside a VM, and how to decode JWT tokens to understand exactly what is wrong.
AADSTS error codes and what they mean
AADSTS error codes appear in browser redirect URLs and in application logs when using the Microsoft Authentication Library (MSAL) or direct OAuth flows. The most common ones:
AADSTS50058 — No session found
AADSTS50058: A silent sign-in request was sent but no user is signed in.
The cookies used to represent the user's session were not sent in the request
to Azure AD. This can happen in an Azure AD federated login scenario.The application called acquireTokenSilent() but no valid session or refresh token exists. The fix is to call acquireTokenPopup() or acquireTokenRedirect() to start an interactive flow.
AADSTS70011 — Invalid scope
AADSTS70011: The provided value for the input parameter 'scope' is not valid.
The scope https://myapi.example.com/.default is not valid.The scope parameter contains a resource URI that is not registered as the Application ID URI in the app registration, or the URI does not end in /.default for client credentials flow. Check the app registration’s “Expose an API” section to verify the Application ID URI.
AADSTS65001 — Consent required
AADSTS65001: The user or administrator has not consented to use the application
with ID 'xxxxxxxx' named 'My App'. Send an interactive authorization request
for this user and resource.The application is requesting permissions that the user or admin has not yet granted. For delegated permissions, the user needs to consent interactively. For application permissions (client credentials flow), an admin must grant admin consent in the App Registration portal.
AADSTS700016 — Application not found in tenant
AADSTS700016: Application with identifier 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
was not found in the directory 'mytenant.onmicrosoft.com'. This can happen if
the application has not been installed by the administrator of the tenant,
or consented to by any user in the tenant.The client is authenticating against the wrong tenant, or the app registration has not been created in this tenant. For multi-tenant apps, the app must be consented to in each tenant separately.
AADSTS90002 — Tenant not found
AADSTS90002: Tenant 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' not found.
This may happen if there are no active subscriptions for the tenant.The tenant ID in the authentication URL is wrong or the tenant has been deleted. Verify the correct tenant ID:
az account show --query tenantId -o tsv
# Or for a specific subscription
az account list \
--query "[?name=='mySubscription'].tenantId" -o tsvManaged identity token acquisition failures
Managed identity authentication uses the Azure Instance Metadata Service (IMDS) endpoint at http://169.254.169.254. Applications running on VMs, App Service, Functions, AKS pods, and Container Instances use this endpoint to get tokens without credentials.
The most common managed identity token error:
ManagedIdentityCredential authentication failed.
Response: {"error":"invalid_resource","error_description":"AADSTS500011:
The resource principal named https://management.azure.com was not found
in the tenant named 'mytenant'."}This usually means the resource URI is wrong. The resource parameter must exactly match the service’s expected audience:
| Azure Service | Resource URI |
|---|---|
| Azure Resource Manager | https://management.azure.com/ |
| Azure Storage | https://storage.azure.com/ |
| Azure Key Vault | https://vault.azure.net |
| Azure SQL | https://database.windows.net/ |
| Azure Event Hubs | https://eventhubs.azure.net/ |
| Microsoft Graph | https://graph.microsoft.com/ |
Note the trailing slashes — they must match exactly for some services.
Test token acquisition from inside the VM:
# Get a token for Azure Resource Manager
curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
| python3 -m json.tool
# For a user-assigned managed identity, include the client_id
curl -s -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/&client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
| python3 -m json.toolA successful response includes access_token, expires_on, and resource. If the request returns connection refused, managed identity is not enabled on the resource.
Enable system-assigned managed identity on a VM:
az vm identity assign \
--resource-group myResourceGroup \
--name myVMEnable on App Service:
az webapp identity assign \
--resource-group myResourceGroup \
--name myWebAppGetting a valid token from IMDS does not mean the identity is authorized to call the target API. A token proves identity but authorization is still controlled by RBAC. If token acquisition succeeds but API calls return 403, the managed identity lacks the required role on the target resource.
Decoding JWT tokens to diagnose errors
JWT tokens contain three base64-encoded sections separated by dots: header, payload, and signature. The payload contains all the claims that determine what the token is valid for.
Decode a token from the command line:
TOKEN="eyJ0eXAiOiJKV1QiLCJhbGciOi..."
# Decode the payload (second section between the dots)
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.toolKey claims to inspect:
{
"aud": "https://management.azure.com/",
"iss": "https://sts.windows.net/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"exp": 1710003600,
"appid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"oid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scp": "user_impersonation",
"tid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}aud(audience): the resource the token is valid for. If this does not match the API you are calling, you get 401. Tokens are not cross-resource.exp(expiration): Unix timestamp when the token expires. Compare withdate +%son Linux.oid(object ID): the authenticated identity’s object ID. Use this to look up RBAC assignments.tid(tenant ID): the tenant the token was issued in. If this does not match the resource’s tenant, the call fails.scp(scope): delegated permissions (user context). Required permission must be listed here.roles: application permissions (app-only context, client credentials flow).
For a web-based token decoder that shows human-readable claim names, paste the token at https://jwt.ms — it decodes client-side only.
Service principal secret expired
When a service principal’s client secret expires, all applications using it begin failing with:
AADSTS7000222: The provided client secret keys for app 'xxxxxxxx' are expired.
Visit the Azure Portal to create new keys for your app:
https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Credentials/appId/xxxxxxxxFind service principals with secrets expiring in the next 30 days:
az ad app list --all \
--query "[].{appId: appId, displayName: displayName}" -o json \
| python3 -c "
import json, sys, subprocess
from datetime import datetime, timezone
apps = json.load(sys.stdin)
now = datetime.now(timezone.utc)
for app in apps:
result = subprocess.run(
['az', 'ad', 'app', 'credential', 'list',
'--id', app['appId'],
'--query', '[].endDateTime', '-o', 'json'],
capture_output=True, text=True
)
dates = json.loads(result.stdout or '[]')
for d in dates:
try:
exp = datetime.fromisoformat(d.replace('Z', '+00:00'))
days_left = (exp - now).days
if days_left < 30:
print(f\"{app['displayName']}: expires in {days_left} days\")
except:
pass
"Rotate the secret:
APP_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
NEW_SECRET=$(az ad app credential reset \
--id "$APP_ID" \
--years 1 \
--query password -o tsv)
echo "New secret generated — store in Key Vault immediately."
# Store it: az keyvault secret set --vault-name myKV --name sp-secret --value "$NEW_SECRET"Consider migrating from client secrets to federated credentials (workload identity federation) for CI/CD pipelines. Federated credentials do not expire and use OIDC tokens from GitHub Actions, Azure DevOps, or other OIDC providers without storing any secrets.
Token audience mismatch
A common error when calling custom APIs is using a token acquired for the wrong resource:
HTTP 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The audience 'https://management.azure.com/' is invalid"The token was issued for Azure Resource Manager but the custom API validates for its own Application ID URI. The application must acquire a token specifically for the target API:
# Get token for a custom API with its Application ID URI
az account get-access-token \
--resource "api://my-api-app-id" \
--query accessToken -o tsvIn MSAL (Python):
import msal
app = msal.ConfidentialClientApplication(
client_id=CLIENT_ID,
client_credential=CLIENT_SECRET,
authority=f"https://login.microsoftonline.com/{TENANT_ID}"
)
# Scope: Application ID URI + /.default
result = app.acquire_token_for_client(
scopes=["api://my-api-app-id/.default"]
)
if "access_token" in result:
token = result["access_token"]
else:
print(f"Error: {result.get('error')}: {result.get('error_description')}")The API validates the aud claim in the received token against its own Application ID URI. If they do not match, the call is rejected with 401 regardless of the token being otherwise valid.
Multi-tenant app registration issues
Multi-tenant applications must be consented to in each tenant. An attempt to use a multi-tenant app in a tenant where consent has not been granted returns:
AADSTS65001: The user or administrator has not consented to use the
application with ID 'xxxxxxxx' named 'My API'.Check the app registration’s supported account type:
az ad app show \
--id "$APP_ID" \
--query "signInAudience" \
-o tsv
# Returns: AzureADMultipleOrgs (multi-tenant) or AzureADMyOrg (single-tenant)For admin consent, the target tenant administrator must visit:
https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={app-id}Or grant admin consent via CLI when logged into the target tenant:
# Run as an admin of the target tenant
az ad app permission admin-consent --id "$APP_ID"For application permissions (client credentials flow), admin consent is always required — there is no user-level consent path. Check if consent has been granted:
az ad app permission list-grants \
--id "$APP_ID" \
--query "[].{consentType: consentType, scope: scope, principalId: principalId}" \
--output tableCommon mistakes
- Using a token acquired for one resource to call a different resource. Azure AD tokens are audience-bound. A token for
https://management.azure.com/cannot be used to call Key Vault or a custom API. Each API call requires a token acquired specifically for that resource URI. Always check theaudclaim in the token matches the API being called. - Not handling managed identity failures gracefully in application code. IMDS token acquisition can fail transiently during VM startup, scale-out events, or platform maintenance. Applications must implement retry with exponential backoff for IMDS calls. Treating the first failure as fatal causes unnecessary crashes during normal Azure platform events.
- Enabling managed identity but not assigning any RBAC roles. Enabling managed identity on a VM creates the identity but grants no permissions anywhere. The identity must then be assigned the appropriate RBAC role on each target resource. A newly enabled managed identity with no role assignments will get valid tokens but every API call will return 403 Forbidden.
- Hardcoding tenant IDs without testing across tenants for multi-tenant apps. Single-tenant app registrations reject authentication attempts from other tenants with AADSTS700016. If an application is meant to support multiple customers in different Azure AD tenants, the app registration must use “Accounts in any organizational directory” and each customer tenant must grant consent separately.
Summary
- AADSTS error codes are specific: 50058 means no session, 70011 means wrong scope, 65001 means consent not granted, 7000222 means expired secret — each has a different fix.
- Test managed identity token acquisition with a curl request to the IMDS endpoint at 169.254.169.254 from inside the compute resource before debugging application code.
- Decode JWT tokens at jwt.ms or with base64 on the command line to inspect the audience, expiry, tenant, and granted permissions.
- Getting a valid token does not mean authorization succeeds — RBAC roles must still be assigned on the target resource for API calls to succeed.
Frequently asked questions
What does AADSTS50058 mean?
AADSTS50058 means no user session was found — the user is not signed in and the application attempted a silent token acquisition that failed. The application needs to trigger an interactive login flow to establish a session. This commonly appears in single-page applications that try to call an API without a valid refresh token.
How do I test if the IMDS endpoint is reachable from a VM for managed identity?
SSH into the VM and run: curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/". A successful response returns a JSON object with an access_token field. If the connection is refused or times out, managed identity is not enabled on the VM or a security tool is blocking the metadata endpoint.
How do I decode a JWT token to diagnose authentication errors?
Go to jwt.ms in a browser and paste the token. It decodes the header and payload without sending the token to any server — jwt.ms is a client-side only tool. Key claims to check: aud (audience/resource), iss (issuer/tenant), exp (expiry time), scp or roles (permissions granted), and oid (object ID of the authenticated identity).