AADSTS50013 fix: invalid assertion in on-behalf-of flow
Short answer: AADSTS50013 means Entra rejected an assertion, usually the user's access token that a middle-tier API tried to exchange in the on-behalf-of (OBO) flow. Microsoft lists issuer mismatch, expiry and malformed tokens as causes. One reported cause is an API that was handed a token meant for Microsoft Graph instead of for itself. Low attack value; fix the token the API receives.
Fix: Developer: make the client request a token for your own API's scope (`api://<api-client-id>/<scope>`), not for Microsoft Graph, and pass that token as the OBO assertion. Don't use an app with a custom signing key as the middle tier. User: report the error to the app's developer. Full steps
| Code | AADSTS50013 |
|---|---|
| Microsoft name | InvalidAssertion |
| Category | Tokens and sessions |
| Type | Failure |
| Attack signal | Low: usually configuration or user error |
| Microsoft lookup | login.microsoftonline.com/error?code=50013 |
What does AADSTS50013 mean?
Section titled “What does AADSTS50013 mean?”Microsoft’s description:
InvalidAssertion: Assertion is invalid because of various reasons - The token issuer doesn’t match the API version within its valid time range -expired -malformed - Refresh token in the assertion isn’t a primary refresh token. Contact the app developer.
An assertion is a token that an app hands to Entra ID as proof of something, and Entra ID refused it. The error comes back as invalid_grant, which Microsoft describes as “some of the authentication material (auth code, refresh token, access token, PKCE challenge) was invalid, unparseable, missing, or otherwise unusable”.
Most user reports of 50013 come from the on-behalf-of (OBO) flow. A client calls your API with an access token. Your API sends that token to Entra ID in the assertion parameter and asks for a new token to call another API, often Microsoft Graph. Microsoft’s OBO reference sets the rule for that token: “This token must have an audience (aud) claim of the app making this OBO request (the app denoted by the client-id field). Applications can’t redeem a token for a different app (for example, if a client sends an API a token meant for Microsoft Graph, the API can’t redeem it using OBO. It should instead reject the token.”
The message usually carries a reason in brackets. The ones users post:
- “Assertion failed signature validation. [Reason - Key was found, but use of the key to verify the signature failed.]”
- “Assertion failed signature validation. [Reason - The key was not found.]”
Microsoft doesn’t map these reasons to causes. See the table below for what users found.
Don’t confuse it with 700024. That code is about the client assertion, the signed JWT an app uses to prove its own identity. 50013 is about the assertion being exchanged, in OBO the user’s token.
Is it an attack?
Section titled “Is it an attack?”Usually not. It is a developer error in how the app passes tokens around. Microsoft’s own text ends with “Contact the app developer.”
| Pattern | Likely meaning |
|---|---|
| “Key was found, but use of the key to verify the signature failed”, from a backend calling Graph via OBO | The client sent the API a Microsoft Graph token and the API tried to exchange it. In one Q&A thread the frontend requested Graph scopes and the backend passed that token on; the cause wasn’t confirmed there. Microsoft says Graph tokens have a “proprietary format” and can’t be validated like other tokens; our reading is that this is why the signature check fails. |
“The key was not found”, and the token’s kid only appears in metadata fetched with ?appid= |
The middle-tier app has a custom signing key. A user reports exactly this. Microsoft: “applications with custom signing keys can’t be used as middle-tier APIs in the OBO flow. This includes enterprise applications configured for single sign-on.” |
| 50013 that mentions expiry | The client passed an expired access token. Get a fresh one before calling the API. |
| 50013 for one app, starting right after a release | The new version changed which scope the client requests. |
| 50013 from an unfamiliar IP for an app you own, with no successes | Someone is sending tokens your app didn’t issue to the token endpoint. Our interpretation: a forged or tampered token would also fail signature validation and land here. Not documented by Microsoft, but worth a look. |
A token with a bad signature gets nothing from Entra ID, so the failure itself grants no access.
How do I find it in my logs (KQL)?
Section titled “How do I find it in my logs (KQL)?”Microsoft doesn’t document which sign-in table an OBO failure lands in. OBO is a sign-in “performed by a client app or OS components on behalf of a user”, which fits the non-interactive log, but we haven’t checked. Look in all three:
union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "50013"| summarize Rows = count(), Apps = dcount(AppId) by SourceTableWhich app fails, for which downstream API, and with which reason. We assume ResultDescription carries the bracketed reason; Microsoft documents it only as “the error message or the reason for failure”:
union SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs| where TimeGenerated > ago(7d)| where ResultType == "50013"| extend Reason = extract(@"Reason - ([^\]]+)", 1, ResultDescription)| summarize Rows = count(), Users = dcount(UserPrincipalName), FirstSeen = min(TimeGenerated) by AppDisplayName, AppId, ResourceDisplayName, Reason| sort by Rows desc50013 for apps that had no successful sign-in in the last 14 days. A starting point for spotting tokens sent to an app from somewhere it doesn’t normally run; tune the window to your tenant:
let failing = union SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs | where TimeGenerated > ago(1d) | where ResultType == "50013" | summarize Fails = count(), IPs = make_set(IPAddress, 10) by AppId, AppDisplayName;let working = union SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs | where TimeGenerated > ago(14d) | where ResultType == "0" | distinct AppId;failing| join kind=leftanti working on AppId| sort by Fails descResultType is a string column, so compare it with "50013", not 50013. Non-interactive rows are aggregated when application, user, IP address, status and resource match, so row counts there are not request counts.
How do I fix AADSTS50013?
Section titled “How do I fix AADSTS50013?”For the user: you can’t fix this yourself. Report the error, with the time and the Correlation ID, to whoever runs the app.
For developers:
- Decode the token you pass as
assertion(Microsoft names jwt.ms for debugging only). Itsaudmust be your API’s client ID or App ID URI. If it points at Microsoft Graph (https://graph.microsoft.com), the client asked for the wrong token. - Expose a scope on your API (App registrations > your API > Expose an API) and have the client request that scope, for example
api://<api-client-id>/access_as_user. The accepted Q&A answer gives these steps; the scope name is your choice. - If your API receives a token whose audience is Microsoft Graph rather than your API, reject it. Microsoft’s OBO reference says exactly that: the API “should instead reject the token”. The fix is on the client side: have it request a token for your API’s own scope.
- Check the middle-tier app has no custom signing key (claims mapping, or an enterprise app configured for SAML SSO). Microsoft says those apps can’t be OBO middle tiers. Use a separate app registration for the API.
- Send a fresh, unexpired access token. Don’t cache and replay an old one.
- Set
requested_token_usetoon_behalf_of, as Microsoft requires.
For admins:
- Use the second query to find the app and the reason, and send both to the app’s owner.
- If an enterprise app with SAML SSO is also used as an API, that is the custom-signing-key case above. The fix is a separate app registration, not a setting.
Related codes
- AADSTS700024: Client assertion is not within its valid time range
- AADSTS90023: Entra rejected the request as invalid
- AADSTS500011: The resource principal was not found in the tenant
- AADSTS65001: User or admin has not consented to the app
Sources
- Microsoft Entra authentication and authorization error codes
- Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow
- Access tokens in the Microsoft identity platform
- What are non-interactive user sign-ins in Microsoft Entra?
- SigninLogs table reference (Azure Monitor)
- AADSTS50013: Assertion failed signature validation (Microsoft Q&A)
- 50013 Key Not Found issue in OBO flow (Microsoft Q&A)
- Getting AADSTS50013 while trying to create GraphServiceClient (Microsoft Q&A)