You are developing an app and see one of the following CORS related errors in the console logs…

  • 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.
  • 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.
  • CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’

Notice it starts with “https://login.microsoftonline.com“, you might have a Azure B2C scenario, so in that case it might start with “https://youdomain.b2clogin.com/…

It is outside of scope for this article If the error is not generated by Azure AD/Entra ID, and the error looks something like this…

Access to XMLHttpRequest at ‘https://app.contoso.com/…

We do not provide guidance on how to resolve CORS for external domains. More than likely, you will need to enable CORS in that environment.

The following articles provides “some” additional guidance…

Let’s get started

First we need to understand what is CORS…

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) 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 that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

For more information about CORS…

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Browsers will not allow Cross Origin requests when the resource does not have the supporting headers. Cross Origin requests originate from an JavaScript XMLHttpRequest which is kind of like a direct HTTP call with no user interaction or window. Azure AD/Entra ID does not have CORS enabled while performing an interactive sign-in which means you cannot send a CORS request to Azure AD/Entra ID.

Depending on the root cause, there may be different solutions. It would be best to collect a Fiddler capture to try to determine your scenario and the cause.

Within the Fiddler capture, look for your XMLHttpRequest request (This is usually or AJAX or JQuery call) and notice it’s a 302 redirect to “https://login.microsoftonline.com.com/…

The HTTP Request and response might look something like this… 

REQUEST
GET https://login.microsoftonline.com.com/domain.onmicrosoft.com/oauth2/v2.0/authorize?... HTTP/1.1
Host: login.microsoftonline.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
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
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
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Origin: https://app.domain.com

RESPONSE

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache
Pragma: no-cache
Content-Type: text/html; charset=utf-8
Expires: -1
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-DNS-Prefetch-Control: on
P3P: CP="DSP CUR OTPi IND OTRi ONL FIN"
Set-Cookie:  ...
Referrer-Policy: strict-origin-when-cross-origin
Date: Tue, 24 Nov 2020 19:08:05 GMT
Content-Length: 194559
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
<!DOCTYPE html>
<html dir="ltr" class="" lang="en">
<head>

Notice there is a Origin header in the request and no ‘Access-Control-Allow-Origin’ header in the response.

We often see this when you try to pass a access token (or Authentication cookie) to the XMLHttpRequest endpoint, the security token whether it’s an access token or authentication cookie gets invalidated and the API redirects to the Azure AD/Entra ID sign-in page instead of throwing a proper 401 HTTP status code. Because of this redirect, and we do not support CORS, browser will throw a CORS error.

So, how do you resolve or workaround this?

Preferably, the solution would be for you to implement your application architecture to follow the OAuth2 and OIDC model. That is, the front end app acquires a access token and passes it in a authorization header to the API (this is what your making your XMLHttpRequest to). Please follow our samples…

https://learn.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code?tabs=apptype#single-page-applications

Solutions to consider based on scenario

Not every scenario will be listed here as every environment and app architecture is different, however, these are the most common.

Scenario: Web App + Web API using authentication cookie

Otherwise meaning your app purely uses cookie authentication and does not follow the proper OAuth2 and Open ID Connect implementation. Your web app or framework is making XMLHttpRequest calls to its API endpoint and using the Web Apps authentication cookie.

When reviewing the Fiddler capture and looking at the XMLHttpRequest request, the XMLHttpRequest might look something like this (Notice the cookie)…

GET https://app.domain.com/… HTTP/1.1

Host: login.microsoftonline.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
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
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
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Origin: https://app.domain.com
Cookie: .AspNet.Cookies=xyz…

If your using Asp.Net or Asp.Net Core. You will need to customize your Azure AD/Entra ID Configuration to not use the token lifetime as the session lifetime.

For more information see…

You can configure the API authentication to thrown an error instead of performing a redirect. This is just an example if your using Asp.Net Core…

services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{       
  options.Events.OnRedirectToIdentityProvider = (context) =>
  {
    if (!context.Request.Headers["Origin"].IsNullOrEmpty())
    {
      context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
      context.HandleResponse();
    }
                    
    return Task.FromResult(true);
  };
}

Then, you can implement additional XMLHttpRequest logic to check if the request is done and whether it was a redirect or a 401. You will need to handle this to tell the client to have the user sign-in again. In most cases, simply refreshing the page will allow the user re-authenticate. For example…

client.onreadystatechange = () => {
  // API call failed (401) or there was a redirect
  if ((client.readyState === client.DONE && client.responseURL == "") || client.Status == 401) {
    // Handle error such as Refreshing page should allow user to re-authenticate
    window.location.reload(true)
  }
};

Scenario: Standalone API using access token

When reviewing the Fiddler capture and looking at the XMLHttpRequest request, it might look something like this (Notice the Authorization header)…

GET https://app.domain.com/… HTTP/1.1

Host: login.microsoftonline.com
Connection: keep-alive
Authorization: Bearer eyJ0…
Upgrade-Insecure-Requests: 1
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
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
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Origin: https://app.domain.com

Send a valid token

If you are passing a access token to your API resource, ensure the token is valid in the first place. Check to see if the token is expired and if it is, request for a new access token.

If your using MSAL.js, then use acquireTokenSilent everytime to acquire a new token before passing it to your API. Do not cache this token yourself. Always use acquireTokenSilent to get the cached token directly from MSAL.

https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-acquire-token?tabs=javascript2

Here is just a example of how it looks when passing a token to an API…

Build single-page app calling a web API – Microsoft Entra | Microsoft Learn

Use JWT Bearer authentication

Ensure JWT Bearer authentication is used (Not Open ID Connect authentication). This implementation will vary depending on the authentication middleware you are using. Please review the docs for your authentication middleware as each middleware has its own implementation strategy. JWT Bearer Authentication should throw a 401 error to the client if the token is not valid. The client should handle the error and request for a new token as needed. If Open ID Connect Authentication scheme is used, the API will attempt to redirect the request to Azure AD/Entra ID or B2C, and this is what will cause the CORS error and it is much harder for the client to handle this scenario.

Here are just a couple examples on how to set up JWT Bearer authentication (Look at the ToDoService/API portion of the samples)

https://learn.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code?tabs=apptype#web-api

Scenario: If the issue is occurring while using MSAL.js to acquire a token

Make sure you configure, authority, knownAuthorities and protocolMode correctly if your using B2C or third-party IdP.

//…
import { ProtocolMode } from '@azure/msal-common';
//…
function MSALInstanceFactory(): IPublicClientApplication {
  return new PublicClientApplication({
    auth: {
      authority: 'https://contoso.b2clogin.com/tfp/655e51e9-be5e-xxxx-xxxx-38aa6558xxxx/b2c_1_susi/v2.0/',
      clientId: 'fb2ad7b7-2032-4a66-8723-e993eb4b9004',
      redirectUri: 'http://localhost:4200',
      knownAuthorities: ['contoso.b2clogin.com'],
      protocolMode: ProtocolMode.OIDC
    },
  });
}

For more information see…

https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md

Scenario: App behind a Load balancer

If your application is behind a load balancer, please check your load balancer for its session lifetime settings. This could be called Session persistence or session affinity.

Scenario: CORS error on Token endpoint

The only supported flow for Single Page Applications is Authorization Code flow with PKCE and Refresh Token flow while having the redirect address configured as Single Page Application.

Based on OAuth2 specs and Security best practices, you should NOT be using Resource Owner Password Credential flow or any of the Confidential Client flows i.e. Client Credentials or On-behalf-of flows.

All other flows will not be supported in Single Page Applications. Azure AD/Entra ID and B2C will not add the CORS headers for the unsupported flows.

Scenario: You are using Azure AD Application Proxy

Please see…
https://learn.microsoft.com/en-us/azure/active-directory/app-proxy/application-proxy-configure-complex-application

Leave a Comment