|
| 1 | +--- |
| 2 | +title: Troubleshoot Cross-Origin Resource Sharing Issues |
| 3 | +description: Helps you troubleshoot and resolve Cross-Origin Resource Sharing issues when using Microsoft Entra ID. |
| 4 | +ms.service: entra-id |
| 5 | +ms.date: 07/08/2025 |
| 6 | +ms.reviewer: willfid, v-weizhu |
| 7 | +ms.custom: sap:Developing or Registering apps with Microsoft identity platform |
| 8 | +--- |
| 9 | +# Troubleshoot CORS issues with Microsoft Entra ID |
| 10 | + |
| 11 | +This article provides guidance on troubleshooting and resolving Cross-Origin Resource Sharing (CORS) errors encountered when using Microsoft Entra ID. |
| 12 | + |
| 13 | +## Understanding CORS |
| 14 | + |
| 15 | +[Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/docs/Web/HTTP/Guides/CORS) is an [HTTP](https://developer.mozilla.org/docs/Glossary/HTTP)-header-based mechanism that allows a server to specify [origins](https://developer.mozilla.org/docs/Glossary/Origin)(domains, schemes, ports) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a "preflight" request to the server hosting the cross-origin resource, in order to check whether the server permits the actual request. During this preflight, the browser sends headers that indicate the HTTP method and headers that are used in the actual request. |
| 16 | + |
| 17 | +For detailed information about CORS headers, refer to [CORS headers](https://developer.mozilla.org/docs/Glossary/CORS). |
| 18 | + |
| 19 | +### Key concepts |
| 20 | + |
| 21 | +- Browsers block cross-origin requests if the resource lacks the supported headers. |
| 22 | +- Cross-origin requests usually originate from JavaScript XMLHttpRequest calls, such as a direct HTTP call with no user interaction or window. |
| 23 | +- Microsoft Entra ID doesn't have CORS enabled while performing an interactive sign-in, meaning CORS requests can't be directly sent to Microsoft Entra ID. |
| 24 | + |
| 25 | +## Symptoms |
| 26 | + |
| 27 | +While developing an application, you might encounter the following CORS-related errors in the browser console logs: |
| 28 | + |
| 29 | +- Example 1 |
| 30 | + |
| 31 | + > Access to XMLHttpRequest at 'https://login.microsoftonline.com/tenant_id/oauth2/v2.0/authorize?client_id=&redirect_uri=signin-oidc&response_type=id_token&scope=openid%20profile&response_mode=form_post&nonce=6370sdfj&state=sdfsdfds-sdfsdfsdf-sd-sdfsdf-T3qwNWW2jRHM&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=5.5.0.0' (redirected from 'xxx') from origin 'yyyy' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. |
| 32 | +
|
| 33 | + The URL starting with `https://login.microsoftonline.com/` indicates that you might have an Azure Active Directory B2C scenario. |
| 34 | +- Example 2 |
| 35 | + |
| 36 | + > Access to fetch at 'https://contosob2c.b2clogin.com/tfp/tenant_id/b2c_1_v2_susi_defaultpage/v2.0/.well-known/openid-configuration' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. |
| 37 | +- Example 3 |
| 38 | + |
| 39 | + > CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present. |
| 40 | +
|
| 41 | +> [!NOTE] |
| 42 | +> |
| 43 | +> - These errors are generated by Microsoft Entra ID. Request URLs in these errors often begin with `https://login.microsoftonline.com` or `https://<your-domain>.b2clogin.com`. The latter typically points to an Azure Active Directory B2C scenario. |
| 44 | +> - If the error doesn't originate from Microsoft Entra ID, it looks like "Access to XMLHttpRequest at `https://app.contoso.com`." This article doesn't provide guidance on resolving external CORS issues. In such cases, you need to configure CORS in that environment. |
| 45 | +
|
| 46 | +## Cause |
| 47 | + |
| 48 | +To identify your scenario and the root cause, capture the failing request using Fiddler. Look for the `XMLHttpRequest` or AJAX request in the Fiddler capture and you see a 302 redirect occurs to `https://login.microsoftonline.com/`. |
| 49 | + |
| 50 | +### Example request and response |
| 51 | + |
| 52 | +Request: |
| 53 | + |
| 54 | +```http |
| 55 | +GET https://login.microsoftonline.com.com/domain.onmicrosoft.com/oauth2/v2.0/authorize?... HTTP/1.1 |
| 56 | +Host: login.microsoftonline.com |
| 57 | +Connection: keep-alive |
| 58 | +Upgrade-Insecure-Requests: 1 |
| 59 | +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 |
| 60 | +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 |
| 61 | +Accept-Encoding: gzip, deflate, br |
| 62 | +Accept-Language: en-US,en;q=0.9 |
| 63 | +Origin: https://app.domain.com |
| 64 | +``` |
| 65 | + |
| 66 | +Response: |
| 67 | + |
| 68 | +```http |
| 69 | +HTTP/1.1 200 OK |
| 70 | +Cache-Control: no-store, no-cache |
| 71 | +Pragma: no-cache |
| 72 | +Content-Type: text/html; charset=utf-8 |
| 73 | +Expires: -1 |
| 74 | +Vary: Accept-Encoding |
| 75 | +Strict-Transport-Security: max-age=31536000; includeSubDomains |
| 76 | +X-Content-Type-Options: nosniff |
| 77 | +X-Frame-Options: DENY |
| 78 | +X-DNS-Prefetch-Control: on |
| 79 | +P3P: CP="DSP CUR OTPi IND OTRi ONL FIN" |
| 80 | +Set-Cookie: ... |
| 81 | +Referrer-Policy: strict-origin-when-cross-origin |
| 82 | +Date: Tue, 24 Nov 2020 19:08:05 GMT |
| 83 | +Content-Length: 194559 |
| 84 | +<!-- Copyright (C) Microsoft Corporation. All rights reserved. --> |
| 85 | +<!DOCTYPE html> |
| 86 | +<html dir="ltr" class="" lang="en"> |
| 87 | +<head> |
| 88 | +``` |
| 89 | + |
| 90 | +You can notice that the request contains an `Origin` header, but no `Access-Control-Allow-Origin` header is present in the response. When trying to pass an access token or authentication cookie via an `XMLHttpRequest` endpoint, the security token becomes invalid. Instead of returning a 401 HTTP status code, the API redirects to the Microsoft Entra ID sign-in page. Because of this redirect, and Microsoft Entra ID doesn't support CORS for interactive sign-ins, the web browser throws a CORS error. |
| 91 | + |
| 92 | +## General solution |
| 93 | + |
| 94 | +Implement your application architecture to follow the OAuth2 and OIDC standards. This solution can ensure your front-end application acquires an access token and includes it in the `Authorization` header of the request when you make your `XMLHttpRequest` to the API. Here are some [single-page application samples](/entra/identity-platform/sample-v2-code?tabs=apptype). |
| 95 | + |
| 96 | +## Scenario-based solutions |
| 97 | + |
| 98 | +Here are the most common scenarios. Not every scenario is listed in this article as every environment and app architecture is different. |
| 99 | + |
| 100 | +### Scenario 1: Web app and Web API using authentication cookies |
| 101 | + |
| 102 | +If your web app or framework makes `XMLHttpRequest` calls to its API endpoint and uses the Web Apps authentication cookies, examine the `XMLHttpRequest` request in the Fiddler capture. It might look like this: |
| 103 | + |
| 104 | +```http |
| 105 | +GET https://app.domain.com/… HTTP/1.1 |
| 106 | +
|
| 107 | +Host: login.microsoftonline.com |
| 108 | +Connection: keep-alive |
| 109 | +Upgrade-Insecure-Requests: 1 |
| 110 | +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 |
| 111 | +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 |
| 112 | +Accept-Encoding: gzip, deflate, br |
| 113 | +Accept-Language: en-US,en;q=0.9 |
| 114 | +Origin: https://app.domain.com |
| 115 | +Cookie: .AspNet.Cookies=xyz… |
| 116 | +``` |
| 117 | + |
| 118 | +If you use ASP.NET or ASP.NET Core, configure Microsoft Entra ID to avoid using token lifetime as the session lifetime. For more information, see [Customize middleware authentication ticket to extend user sign-in duration](customize-authentication-session-expiration.md). You can configure the API authentication to throw an error instead of performing a redirect. For ASP.NET Core, you can use the following code: |
| 119 | + |
| 120 | +```csharp |
| 121 | + services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => |
| 122 | +{ |
| 123 | + options.Events.OnRedirectToIdentityProvider = (context) => |
| 124 | + { |
| 125 | + if (!context.Request.Headers["Origin"].IsNullOrEmpty()) |
| 126 | + { |
| 127 | + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; |
| 128 | + context.HandleResponse(); |
| 129 | + } |
| 130 | + |
| 131 | + return Task.FromResult(true); |
| 132 | + }; |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Then, implement extra `XMLHttpRequest` logic to check if the request is complete and it's a redirect or a 401 error. You must perform the action to tell the client to have the user sign-in again. In most cases, refreshing the page allows the user to reauthenticate. Here's a code exmaple: |
| 137 | + |
| 138 | +```http |
| 139 | + client.onreadystatechange = () => { |
| 140 | + // API call failed (401) or there was a redirect |
| 141 | + if ((client.readyState === client.DONE && client.responseURL == "") || client.Status == 401) { |
| 142 | + // Handle error such as Refreshing page should allow user to re-authenticate |
| 143 | + window.location.reload(true) |
| 144 | + } |
| 145 | +}; |
| 146 | +``` |
| 147 | + |
| 148 | +### Scenario 2: Standalone API using access tokens |
| 149 | + |
| 150 | +Review the Fiddler capture and look at the XMLHttpRequest request, it might look like this (notice the Authorization header): |
| 151 | + |
| 152 | +```http |
| 153 | +GET https://app.domain.com/… HTTP/1.1 |
| 154 | +
|
| 155 | +Host: login.microsoftonline.com |
| 156 | +Connection: keep-alive |
| 157 | +Authorization: Bearer eyJ0… |
| 158 | +Upgrade-Insecure-Requests: 1 |
| 159 | +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 |
| 160 | +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 |
| 161 | +Accept-Encoding: gzip, deflate, br |
| 162 | +Accept-Language: en-US,en;q=0.9 |
| 163 | +Origin: https://app.domain.com |
| 164 | +``` |
| 165 | +
|
| 166 | +To resolve the issue in this scenario, use one of the following methods: |
| 167 | +
|
| 168 | +- [Send a valid token](#method-1-send-a-valid-token) |
| 169 | +- [Use JWT bearer authentication](#method-2-use-jwt-bearer-authentication) |
| 170 | +
|
| 171 | +#### Method 1: Send a valid token |
| 172 | +
|
| 173 | +If you pass an access token to your API resource, ensure the token is valid. Check if the token is expired. If it is, request a new access token. If you use Microsoft Authentication Library for JavaScript (MSAL.js), use `acquireTokenSilent` every time to acquire a new token before passing it to your API. Don't cache this token yourself. Always use `acquireTokenSilent` to get the cached token directly from MSAL. |
| 174 | +
|
| 175 | +For more information, see [Single-page application: Acquire a token to call an API](/entra/identity-platform/scenario-spa-acquire-token). |
| 176 | +
|
| 177 | +Here's is an example of how it looks when passing a token to an API: [Single-page application: Call a web API](/entra/identity-platform/scenario-spa-call-api). |
| 178 | +
|
| 179 | +#### Method 2: Use JWT bearer authentication |
| 180 | +
|
| 181 | +Use JWT Bearer authentication instead of Open ID Connect. This implementation depends on your authentication middleware, so review its documentation because each middleware has its own implementation strategy. JWT Bearer Authentication should throw a 401 error to the client if the token isn't valid. The client should handle the error and request a new token as needed. If Open ID Connect Authentication scheme is used, the API tries to redirect the request to Microsoft Entra ID or B2C, leading to CORS errors. It's difficult for the client to handle this scenario. |
| 182 | +
|
| 183 | +Here are a couple examples on how to set up JWT Bearer authentication: [Microsoft identity platform code samples for authentication and authorization](/entra/identity-platform/sample-v2-code#web-api). |
| 184 | +
|
| 185 | +### Scenario 3: Using MSAL.js with B2C or third-party IdP |
| 186 | +
|
| 187 | +Make sure you configure `authority`, `knownAuthorities`, and `protocolMode` correctly. |
| 188 | +
|
| 189 | +```http |
| 190 | +//… |
| 191 | +import { ProtocolMode } from '@azure/msal-common'; |
| 192 | +//… |
| 193 | +function MSALInstanceFactory(): IPublicClientApplication { |
| 194 | + return new PublicClientApplication({ |
| 195 | + auth: { |
| 196 | + authority: 'https://contoso.b2clogin.com/tfp/655e51e9-be5e-xxxx-xxxx-38aa6558xxxx/b2c_1_susi/v2.0/', |
| 197 | + clientId: 'fb2ad7b7-2032-4a66-8723-e993eb4b9004', |
| 198 | + redirectUri: 'http://localhost:4200', |
| 199 | + knownAuthorities: ['contoso.b2clogin.com'], |
| 200 | + protocolMode: ProtocolMode.OIDC |
| 201 | + }, |
| 202 | + }); |
| 203 | +} |
| 204 | +``` |
| 205 | +
|
| 206 | +For more information, see [MSAL.js configuration options](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md). |
| 207 | +
|
| 208 | +### Scenario 4: App is behind a load balancer |
| 209 | +
|
| 210 | +If your application is behind a load balancer, check the session lifetime settings of your load balancer, such as **Session persistence** or session affinity. |
| 211 | +
|
| 212 | +### Scenario 5: CORS error on token endpoint |
| 213 | +
|
| 214 | +The only supported flow for Single Page Applications is Authorization Code Flow with Proof Key for Code Exchange (PKCE) and Refresh Token Flow while having the redirect address configured as a Single Page Application. |
| 215 | +
|
| 216 | +Based on OAuth2 specs and Security best practices, don't use the following flows: |
| 217 | +
|
| 218 | +- Resource Owner Password Credential (ROPC) |
| 219 | +- Confidential Client flows, such as Client Credentials or On-behalf-of flows |
| 220 | +
|
| 221 | +All other flows aren't supported in Single Page Applications. Microsoft Entra ID and B2C don't add the CORS headers for the unsupported flows. |
| 222 | +
|
| 223 | +### Scenario 6: Using Microsoft Entra Application Proxy |
| 224 | +
|
| 225 | +If your app uses Microsoft Entra Application Proxy, see [Understand complex applications in Microsoft Entra application proxy](/entra/identity/app-proxy/application-proxy-configure-complex-application). |
| 226 | +
|
| 227 | +## References |
| 228 | +
|
| 229 | +- [Enable Cross-Origin Requests (CORS) in ASP.NET Core](/aspnet/core/security/cors) |
| 230 | +- [Enable Cross-Origin Requests in ASP.NET Web API 2](/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api) |
| 231 | +- [Azure App Service REST API tutorial](/azure/app-service/app-service-web-tutorial-rest-api) |
| 232 | +- [Azure API Management CORS policy](/azure/api-management/cors-policy) |
| 233 | +
|
| 234 | +[!INCLUDE [Azure Help Support](../../../includes/azure-help-support.md)] |
0 commit comments