{
  "schemaVersion": "1.0",
  "code": "90009",
  "name": "TokenForItselfMissingIdenticalAppIdentifier",
  "title": "AADSTS90009 fix: app requests a token for itself",
  "url": "https://brambrinkmeier.com/entra/aadsts/90009/",
  "alternates": {
    "markdown": "https://brambrinkmeier.com/entra/aadsts/90009.md",
    "json": "https://brambrinkmeier.com/api/aadsts/90009.json",
    "html": "https://brambrinkmeier.com/entra/aadsts/90009/"
  },
  "description": "AADSTS90009: an app asked for a token for itself without using its GUID app ID. A developer error, no attack value; fix the resource or scope.",
  "summary": "AADSTS90009 means an app asked Microsoft Entra ID for a token where the resource is the app itself, but named itself with an App ID URI or other string instead of its GUID application ID. Microsoft supports that only with the GUID. It is a developer error with no attack value. Use the client ID GUID as the resource, or call the right API.",
  "inMicrosoftReference": true,
  "unlistedChecked": null,
  "meaningText": "Microsoft's description:\n\n> **TokenForItselfMissingIdenticalAppIdentifier**: The application is requesting a token for itself. This scenario is supported only if the resource that's specified is using the GUID-based application ID.\n\nIn a token request the app names two things: itself (the client, by its application ID) and the API it wants a token for (the resource). Here both point at the same app. Entra ID accepts that only if the resource is given as the app's **GUID application ID**. If the resource is given another way, such as the App ID URI, the request fails with 90009.\n\nAn app can be named by more than its GUID. Microsoft describes the App ID URI as a string a resource sets *\"as its identifier\"*, which *\"Clients who acquire a token for the API can use … during an OAuth request\"*. The service principal's `servicePrincipalNames` hold those URIs. Microsoft recommends `api://<appId>` as the format. So a request with resource `api://<your client ID>` or `https://yourdomain/...` can point back at your own app without containing the bare GUID.\n\nThe full message users report reads: *\"AADSTS90009: Application '<id>'(<name>) is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App Identifier.\"*",
  "sections": [
    {
      "heading": "What does AADSTS90009 mean?",
      "text": "Microsoft's description:\n\n> **TokenForItselfMissingIdenticalAppIdentifier**: The application is requesting a token for itself. This scenario is supported only if the resource that's specified is using the GUID-based application ID.\n\nIn a token request the app names two things: itself (the client, by its application ID) and the API it wants a token for (the resource). Here both point at the same app. Entra ID accepts that only if the resource is given as the app's **GUID application ID**. If the resource is given another way, such as the App ID URI, the request fails with 90009.\n\nAn app can be named by more than its GUID. Microsoft describes the App ID URI as a string a resource sets *\"as its identifier\"*, which *\"Clients who acquire a token for the API can use … during an OAuth request\"*. The service principal's `servicePrincipalNames` hold those URIs. Microsoft recommends `api://<appId>` as the format. So a request with resource `api://<your client ID>` or `https://yourdomain/...` can point back at your own app without containing the bare GUID.\n\nThe full message users report reads: *\"AADSTS90009: Application '<id>'(<name>) is requesting a token for itself. This scenario is supported only if resource is specified using the GUID based App Identifier.\"*"
    },
    {
      "heading": "Is it an attack?",
      "text": "No. A request that names itself badly gets nothing. Users report it in three situations:\n\n| Pattern | Likely meaning |\n|---|---|\n| An MSAL.js app gets 90009 from `acquireTokenSilent` when asking for a token for its own App Service API | Reported in Microsoft Q&A. The thread doesn't show the scope used; the error says the app named itself without its GUID. |\n| A script or service account asks for a scope with the app's own custom-domain URI | The scope was meant for another API but named the calling app. In one Microsoft Q&A thread a moderator found exactly that: *\"the value in scope should be the client-id or application ID URI of your web API\"*, not the caller's. |\n| A password-grant (ROPC) request through an identity broker such as Auth0 | The broker sends a resource or audience that resolves to its own Entra app. Reported in the Auth0 community. |\n\nNone of these is a security signal. In the second row the app didn't mean to ask for itself at all, so check that first."
    },
    {
      "heading": "How do I find it in my logs (KQL)?",
      "text": "Microsoft doesn't document which sign-in table 90009 lands in, or whether it is logged at all. Our interpretation is that it may be rejected before a sign-in row is written, as a malformed request. Check your workspace:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(), Apps = dcount(AppId) by SourceTable\n```\n\nIf rows exist, which apps request tokens for themselves:\n\n```kusto\nunion SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(),\n            Users = dcount(UserPrincipalName),\n            Resources = make_set(ResourceIdentity, 5),\n            FirstSeen = min(TimeGenerated),\n            LastSeen = max(TimeGenerated)\n          by AppId, AppDisplayName = coalesce(AppDisplayName, ServicePrincipalName)\n| sort by Rows desc\n```\n\nIf nothing comes back, the error message the developer got is the evidence. It names the application ID. `ResultType` is a **string** column, so compare it with `\"90009\"`."
    },
    {
      "heading": "How do I fix AADSTS90009?",
      "text": "**For developers:**\n1. Decide which API the token is really for. If it is a **different** API, put that API's identifier in the scope, for example `api://<web API client ID>/.default` or `<web API client ID>/.default`. Then add that API under your app's **API permissions** and get consent.\n2. If your app really needs a token for **itself** (one registration for front end and API), give the resource as the GUID application ID, for example a scope of `<your client ID>/.default` or `<your client ID>/<scope>`. Microsoft notes that some APIs *\"expect only the app ID (a GUID) and the scope name\"* in the scope.\n3. With the v1.0 endpoint or a library that sends `resource`, set `resource` to the client ID GUID, not the App ID URI. The Auth0 thread quotes a Stack Overflow answer with this advice; the poster didn't confirm it worked.\n4. For app-only tokens, Microsoft's rule is that *\"The scope to request for a client credential flow is the name of the resource followed by `/.default`\"*.\n\n**For admins:** nothing to change in the tenant. Send the app ID from the error to the developer."
    }
  ],
  "microsoftWording": {
    "text": "**TokenForItselfMissingIdenticalAppIdentifier**: The application is requesting a token for itself. This scenario is supported only if the resource that's specified is using the GUID-based application ID.",
    "source": "Microsoft's error code reference, as quoted on the page",
    "sourceUrl": "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes"
  },
  "securityReading": {
    "category": {
      "id": "app-config",
      "label": "App configuration"
    },
    "kind": {
      "id": "config",
      "label": "Configuration error"
    },
    "attackSignal": {
      "level": "low",
      "label": "Low: usually configuration or user error"
    },
    "mitre": [],
    "tables": [],
    "isItAnAttack": "No. A request that names itself badly gets nothing. Users report it in three situations:\n\n| Pattern | Likely meaning |\n|---|---|\n| An MSAL.js app gets 90009 from `acquireTokenSilent` when asking for a token for its own App Service API | Reported in Microsoft Q&A. The thread doesn't show the scope used; the error says the app named itself without its GUID. |\n| A script or service account asks for a scope with the app's own custom-domain URI | The scope was meant for another API but named the calling app. In one Microsoft Q&A thread a moderator found exactly that: *\"the value in scope should be the client-id or application ID URI of your web API\"*, not the caller's. |\n| A password-grant (ROPC) request through an identity broker such as Auth0 | The broker sends a resource or audience that resolves to its own Entra app. Reported in the Auth0 community. |\n\nNone of these is a security signal. In the second row the app didn't mean to ask for itself at all, so check that first.",
    "conditionalAccess": null
  },
  "kql": [
    {
      "title": null,
      "leadIn": "Microsoft doesn't document which sign-in table 90009 lands in, or whether it is logged at all. Our interpretation is that it may be rejected before a sign-in row is written, as a malformed request. Check your workspace:",
      "query": "union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(), Apps = dcount(AppId) by SourceTable"
    },
    {
      "title": null,
      "leadIn": "If rows exist, which apps request tokens for themselves:",
      "query": "union SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(),\n            Users = dcount(UserPrincipalName),\n            Resources = make_set(ResourceIdentity, 5),\n            FirstSeen = min(TimeGenerated),\n            LastSeen = max(TimeGenerated)\n          by AppId, AppDisplayName = coalesce(AppDisplayName, ServicePrincipalName)\n| sort by Rows desc"
    }
  ],
  "kqlSectionText": "Microsoft doesn't document which sign-in table 90009 lands in, or whether it is logged at all. Our interpretation is that it may be rejected before a sign-in row is written, as a malformed request. Check your workspace:\n\n```kusto\nunion withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(), Apps = dcount(AppId) by SourceTable\n```\n\nIf rows exist, which apps request tokens for themselves:\n\n```kusto\nunion SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs\n| where TimeGenerated > ago(30d)\n| where ResultType == \"90009\"\n| summarize Rows = count(),\n            Users = dcount(UserPrincipalName),\n            Resources = make_set(ResourceIdentity, 5),\n            FirstSeen = min(TimeGenerated),\n            LastSeen = max(TimeGenerated)\n          by AppId, AppDisplayName = coalesce(AppDisplayName, ServicePrincipalName)\n| sort by Rows desc\n```\n\nIf nothing comes back, the error message the developer got is the evidence. It names the application ID. `ResultType` is a **string** column, so compare it with `\"90009\"`.",
  "fix": {
    "short": "Developer: if the app really needs a token for itself, use its application (client) ID GUID as the resource or scope, for example `<client-id>/.default`. More often the scope should name a different API: put that API's App ID URI or client ID in the scope instead of your own.",
    "steps": "**For developers:**\n1. Decide which API the token is really for. If it is a **different** API, put that API's identifier in the scope, for example `api://<web API client ID>/.default` or `<web API client ID>/.default`. Then add that API under your app's **API permissions** and get consent.\n2. If your app really needs a token for **itself** (one registration for front end and API), give the resource as the GUID application ID, for example a scope of `<your client ID>/.default` or `<your client ID>/<scope>`. Microsoft notes that some APIs *\"expect only the app ID (a GUID) and the scope name\"* in the scope.\n3. With the v1.0 endpoint or a library that sends `resource`, set `resource` to the client ID GUID, not the App ID URI. The Auth0 thread quotes a Stack Overflow answer with this advice; the poster didn't confirm it worked.\n4. For app-only tokens, Microsoft's rule is that *\"The scope to request for a client credential flow is the name of the resource followed by `/.default`\"*.\n\n**For admins:** nothing to change in the tenant. Send the app ID from the error to the developer."
  },
  "labReproduced": null,
  "labSectionText": null,
  "labRuns": [],
  "verification": "documentation",
  "related": [
    "500011",
    "700016",
    "65001"
  ],
  "coveredBy": [],
  "sources": [
    {
      "title": "Microsoft Entra authentication and authorization error codes",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/reference-error-codes"
    },
    {
      "title": "Restrictions on identifier URIs of Microsoft Entra applications",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/identifier-uri-restrictions"
    },
    {
      "title": "servicePrincipal resource type (Microsoft Graph)",
      "url": "https://learn.microsoft.com/en-us/graph/api/resources/serviceprincipal"
    },
    {
      "title": "Scopes for a web API accepting v1.0 tokens",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/msal-v1-app-scopes"
    },
    {
      "title": "Acquire tokens to call a web API using a daemon application",
      "url": "https://learn.microsoft.com/en-us/entra/identity-platform/scenario-daemon-acquire-token"
    },
    {
      "title": "SigninLogs table reference (Azure Monitor)",
      "url": "https://learn.microsoft.com/en-us/azure/azure-monitor/reference/tables/signinlogs"
    },
    {
      "title": "Microsoft Q&A: OAuth 2.0 and Azure AD error AADSTS90009 (user report)",
      "url": "https://learn.microsoft.com/en-us/answers/questions/1076516/oauth-2-0-and-azure-active-directory-error-aadsts9"
    },
    {
      "title": "Microsoft Q&A: MSAL Python service account token error (user report)",
      "url": "https://learn.microsoft.com/en-us/answers/questions/764174/msal-python-service-account-token-genration-error"
    },
    {
      "title": "Auth0 Community: Azure AD integration with password grant returns error (user report)",
      "url": "https://community.auth0.com/t/azure-ad-integration-with-password-grant-returns-error/25103"
    }
  ],
  "published": "2026-11-01",
  "lastUpdated": "2026-11-01",
  "lastVerified": null,
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "licenseNote": "CC BY 4.0 covers the author's own text and classification fields. Quoted Microsoft error wording belongs to Microsoft, is quoted with its source and can change.",
  "attribution": "Bram Brinkmeier, \"AADSTS90009 fix: app requests a token for itself\", https://brambrinkmeier.com/entra/aadsts/90009/ (CC BY 4.0)",
  "microsoftErrorLookup": "https://login.microsoftonline.com/error?code=90009"
}
