Skip to content

AADSTS9002326: Cross-origin token request from a non-SPA app (fix)

Short answer: AADSTS9002326 means a browser sent a token request (it carried an Origin header) for an app that isn't registered as a single-page application. Microsoft's error reference doesn't list it. Typical causes: client credentials or a secret called from JavaScript, or an SPA whose redirect URI isn't the spa type. Low attack value. Fix the app registration or move the call to a backend.

Fix: Developer: for an SPA, register the redirect URI under Single-page application, not Web, and use auth code flow with PKCE (MSAL.js). If browser code calls `client_credentials` or sends a secret, move that call to a backend and rotate the secret. User: report the app, page and time to its owner. Full steps

At a glance
CodeAADSTS9002326
In Microsoft's error referenceNo (checked ). Wording on this page is what Entra returned or what users report.
CategoryApp configuration
TypeConfiguration error
Attack signalLow: usually configuration or user error
Microsoft lookuplogin.microsoftonline.com/error?code=9002326

Microsoft’s error code reference doesn’t list AADSTS9002326 (checked 30 October 2026).

The lab reproduced it. A password-grant token request from a public client, sent with the header Origin: https://lab.brambrinkmeier.com, returned:

AADSTS9002326: Cross-origin token redemption is permitted only for the ‘Single-Page Application’ client-type. Request origin: ‘https://lab.brambrinkmeier.com’.

Entra treats a token request that carries an Origin header as one made from a browser. It accepts such a request only when the app is set up as a single-page application (SPA). Microsoft’s wording below links the header to the browser.

Microsoft describes the message, without the code number, in its authorization code flow reference:

Single page apps may receive an invalid_request error indicating that cross-origin token redemption is permitted only for the ‘Single-Page Application’ client-type. This indicates that the redirect URI used to request the token has not been marked as a spa redirect URI.

The same page explains why the check exists:

Applications can’t use a spa redirect URI with non-SPA flows, for example, native applications or client credential flows. […] the Microsoft identity platform also prevents the use of client credentials in all flows in the presence of an Origin header, to ensure that secrets aren’t used from within the browser.

Our reading of the lab result: the check is not only about secrets. The lab request used the password grant from a public client, with no secret, and still got 9002326. So any token request with an Origin header from an app that isn’t an SPA can hit it.

On 30 October 2026 an automated lab run sent a password-grant (ROPC) token request from a public client app in my lab tenant. The request carried the header Origin: https://lab.brambrinkmeier.com. Entra returned AADSTS9002326 with the first line quoted above.

That confirms the code and its wording at the token endpoint. The message names the origin it saw, which helps you find the page that sent the call. It lands in SigninLogs (interactive user sign-ins): one row per lab run, Status Failure, Conditional Access Not Applied, client app “Mobile Apps and Desktop clients”. Entra’s explanation on the row (the “additional details” field) reads: “The application must fix either the reply URIs registered on the application registration to include a unique reply address of type “spa”, or they must fix the token request to not include an Origin header, if being sent from a non-browser client.“ That is Microsoft’s own description of the fix, which its error reference doesn’t carry. The KQL below was run on these rows (how). Both queries found the lab rows.

Almost always no. It is a mismatch between where the code runs (a browser) and how the app is registered.

Pattern Likely meaning
Works in Postman, fails from the web page Users report exactly this (Q&A 1534740, Q&A 1608608). Our reading: Postman sends no Origin header; the browser does.
A script on a SharePoint page or a front end calls client_credentials with a secret Users report it on this call (Q&A 1534740). The secret is in browser code, which Microsoft blocks on purpose. Treat the secret as exposed.
An SPA using MSAL.js fails on the code redemption step The redirect URI is registered as Web, not Single-page application. This is the cause Microsoft describes.
A hybrid mobile app (Ionic/Capacitor) fails with origin capacitor://localhost Users report it (Ionic forum). Likely the app’s web view sends an Origin header, so Entra treats it as a browser (unconfirmed).

As far as we can tell, the error gives an attacker nothing. The request fails before a token is issued. The one security point is the second row. If a client secret sits in JavaScript, anyone who opens the page can read it. Rotate it.

Microsoft doesn’t document which sign-in table 9002326 is logged in. Check where it lands in your workspace:

union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == "9002326"
| summarize Rows = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceTable, AppId

Which apps fail, and from which clients. UserAgent often tells you it is a browser:

union withsource=SourceTable SigninLogs, AADNonInteractiveUserSignInLogs, AADServicePrincipalSignInLogs
| where TimeGenerated > ago(30d)
| where ResultType == "9002326"
| summarize Rows = count(),
IPs = dcount(IPAddress),
Agents = make_set(UserAgent, 5),
Descriptions = make_set(ResultDescription, 3)
by SourceTable, AppId
| sort by Rows desc

If both queries return nothing, the rows may not be written to your tenant’s logs at all. That is also undocumented. The message itself, with its Request origin, is then your best evidence.

For the user: you can’t fix this yourself. Report the app name, the page you were on and the time to whoever runs the app.

For developers:

  1. If the app is an SPA, register the redirect URI under Authentication > Add a platform > Single-page application, not under Web. Microsoft’s auth code flow page says to “update the redirect URI for your app to use the spa type”. Use the authorization code flow with PKCE, for example with MSAL.js. Microsoft notes PKCE is “required by the Microsoft identity platform for single page apps using the authorization code flow”.
  2. If browser code calls client_credentials or sends a client secret, move that call to a backend. Microsoft’s guidance is plain: “Don’t use the application secret in a native app or single page app because a client_secret can’t be reliably stored on devices or web pages.” The browser then calls your backend, and the backend calls Entra.
  3. If browser code uses the password grant, stop. Use an interactive sign-in with MSAL.js instead.
  4. For hybrid mobile apps, our reading is that the web view counts as a browser. Either register the app as an SPA and use a browser-style flow, or use a native MSAL library so the token request doesn’t come from the web view. Test both; the Ionic thread has no replies and no confirmed fix.

For admins:

  1. Look up the app by the AppId from the queries, or by the client ID in the error.
  2. In App registrations > the app > Authentication, check which platform the redirect URI is under.
  3. If the app had a secret in browser code, create a new secret, update the backend that should hold it, and delete the old one.

Sources

  1. Microsoft Entra authentication and authorization error codes
  2. Microsoft identity platform and OAuth 2.0 authorization code flow
  3. Microsoft Q&A: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type (1534740)
  4. Microsoft Q&A: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type (1608608)
  5. Ionic Forum: Capacitor MSAL Azure B2C - iOS not working AADSTS9002326
  6. SigninLogs table reference (Azure Monitor)
  7. AADServicePrincipalSignInLogs table reference (Azure Monitor)