From our website that is using Microsoft Azure AD for authentication, we are encountering occasional CORS errors - only sometimes and only for some users.
This is a website (mixed ASP.NET, .NET Core, and classic ASP) running in IIS on Windows Server 2016. It is on a web farm of 2 servers, with a "sticky session" load-balancer before the farm. We changed to Azure AD (using an AD setup from our corporate security group) a couple of months ago. All websites within the corporation are moving or have moved to Azure AD authentication. This website is available on the public web, and there are a few public pages, but most pages require authentication.
Generally, the Azure authentication has been working well for all users, but occasionally a user will encounter an error during a "partial-page" (AJAX-like) save or edit operation using ASP.NET web forms components from a well-known company that sells .NET UI controls. I suspect, but can't confirm, that these errors only happen when the authentication token needs to be refreshed (and it's happening within a partial-page update).
If the user has the browser developer tools open at the time of the error, they will see an error like the following:
Access to XMLHttpRequest at 'https://login.microsoftonline.com/[...]' (redirected from 'https://WEBSITE/FOLDER/PAGENAME' ) from origin 'https://WEBSITE' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have tried adding various "Access-Control" custom headers in the top-level web.config file for the site, including for a while having a wildcard "Access-Control-Allow-Origin" entry. However, it seems that these settings probably need to be on the microsoftonline site, and not on our website. Currently, I have removed all the "Access-Control" settings.
I previously had an "X-Frame-Options" header set to "SAMEORIGIN", but I have removed that as well.
I do have a "Content-Security-Policy-Report-Only" (report-only) policy with connections allowed to "login.microsoftonline.com" - this is not reporting any potentially blocked connections.
Do you have any suggestions for settings or headers that I can use to make these connections always allowed? Or, any other troubleshooting steps that you might suggest?
EDIT: I am now trying the following in web.config:
Access-Control-Allow-Origin : https://[OUR-WEBSITE]
Access-Control-Allow-Credentials : true
Access-Control-Allow-Methods : GET, POST, OPTIONS
Access-Control-Allow-Headers : Origin, Content-Type, Accept
In the "customHeaders" section; in the form of: [add name="Access-Control-Allow-Credentials" value="true" /], etc.
EDIT-2: This change didn't help - still getting the No 'Access-Control-Allow-Origin' header error.
EDIT ###########################
I have been able to reproduce the problem with a standard/template project in Visual Studio 2022 (without any third-party components).
This is using the latest version/update of Visual Studio 2022, and also using a "personal" Azure AD configuration (using my MSDN account).
Run Visual Studio 2022, select "New Project", select the template "ASP.NET Web Application (.NET Framework)", and click "Next".
Enter a name for the project, make sure that ".NET Framework 4.8" is selected, and click "Create".
At the next screen, select "Web Forms" and (importantly) select "Microsoft identity platform" for the authentication option, and click "Create".
When the project is created and opened in Visual Studio, it will display a dialog about "Required components to be installed", including "dotnet msidentity tool"; click "Next".
After that, it will display a "Microsoft identity platform" screen, where you may be required to sign in (and perhaps create a tenant if you don't already have one created). I already had a tenant and applications owned by that tenant, using my MSDN account. Select (or create) an application, and click "Next".
At the "Summary of changes" screen, click "Finish". When this work finishes, click "Close".
Now, open the "Default.aspx" page in the Visual Studio editor, and at the end, just before the closing "asp:Content" tag, add the following:
<script type="text/javascript">
function refreshSession() {
fetch("Contact.aspx");
}
setInterval(refreshSession, 120000);
</script>
The above JavaScript code makes an XMLHttpRequest /fetch call every 2 minutes to the "Contact.aspx" page (and discards the result). This is a within-page or partial-page request that does not require reloading the current page or opening another page in the application.
Then, run/debug the project open to the Default page (the page where you added the above JavaScript). Open the browser developer tools, and select the "Network" and "Console" items, and wait.
After some period of time (45 minutes, maybe an hour), you will start seeing "[...] blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource" type errors in the Console window in the browser. This is the error that I need to resolve.
EDIT 9-Aug ======================================
I have made some progress, but still don't have a complete answer.
I added the following code segments in the startup code:
CookieSameSite = SameSiteMode.None,
CookieSecure = CookieSecureOption.Always,
[...]
Provider = new CookieAuthenticationProvider()
{
OnResponseSignIn = (context) =>
{
context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddHours(12);
}
},
[...]
new OpenIdConnectAuthenticationOptions
{
UseTokenLifetime = false,
Some of this came from this article: https://blogs.aaddevsup.xyz/2021/04/8044/
This seems to have resolved the situation in all of our single-server environments (workstation, development server, test server, staging server).
However, in our production "web farm" environment (2 IIS web servers with a load-balancer in front), this situation (within-page XMLHttpRequest) still fails with the same type of error message, within about 10 minutes. This may possibly be because the Microsoft Identity refresh token doesn't know which server to return to. (The load-balancer is configured for "sticky sessions".)
The last piece that was missing - the load balancer had a very short timeout set for the "persistent connection cookie"; we asked for that timeout to be increased to 120 minutes. After that, XMLHttpRequest calls within a web page were successful for up to the 120-minute timeout.