Putting it all together – CORS tutorial
Hosting the two sites on IIS and understanding the request flow and needed configuration to allow the entire application work correctly is what we will focus on in this article. This far, in this tutorial we have gone through creating the front-end website using Razor Pages and the back-end HTTP web-service using WebAPI, as well as preparing the two solutions for publishing on IIS as explained in: https://docs.microsoft.com/en-us/aspnet/core/publishing/iis?tabs=aspnetcore2x . Since the application is expected to run using Windows Integrated authentication there will be a couple of more steps that we will need to take in both solutions to make sure that the ASP.net Core interacts correctly with IIS and is forwarded the authentication tokens from the IIS worker process (w3wp.exe).
The first thing to accomplish, is to setup the applications to work with IIS. This is achieved in the Startup.cs class of each of the two projects. We will be adding IIS as a service, in the ConfigureServices method, using the options pattern. The only option we are interested in is having the applications configured for Windows Authentication. This is done by setting the AutomaticAuthentication option and setting to true. The code is shown below:
public void ConfigureServices(IServiceCollection services)
//needed to enable IIS integration with Windows Auth
options.AutomaticAuthentication = true;
The highlighted code is what needs to be added to the ConfigureServices method in both projects, after which they should be recompiled and published.
Inside the IIS Manager Console, I recommend setting two websites (they can be on different servers on the same server) and setting up HTTP bindings with host header entries, for corerazor and corewebapi, by selecting the 'Bindings' action for each website:
Host bindings for CoreRazor – front end website
Host bindings for CoreWebAPI – backend http webservice application
SIDE NOTE: If you plan to host both websites on the same Windows server, and if you have no DNS so that you can setup CNAME aliases for the hostnames coreRazor and coreWebApi , you can modify the hosts file of the computer you will be accessing the website from to map the two entries to your server's IP address. To do this, you may wish to follow the article below: https://www.petri.com/easily-edit-hosts-file-windows-10 .
The second step in configuring the two applications to use Windows Integrated authentication, is to configure the ASP.net Core Platforms Handler to forward the Windows integrated authentication token to the dotnet.exe process that is hosting the application. The easiest way to achieve this is using the Configuration Editor in the IIS Mangeger console. Just select each of your sites, and then double click the 'Configuration Editor' icon in the middle pane:
Once inside this feature, you will be able to access any configuration relating to the website, that is coming either from ApplicationHost.config file or the website's web.config file. Using the dropdown control from the top of the Configuration Editor window, select the System.WebServer/AspNetCore tag from the list of availabe tags as shown:
Within this section, you will find all of the possible configuration for the ASP.net Core Platforms Handler. Among the settings is one called forwardWindowsAuthToken whish should be set to 'true', in order for the module to forward the authentication token to the dotnet.exe process.
Next, we need to understand the request flow, which may be then most important part of this tutorial. Nothing gets the point across better than a visual diagram:
In the above diagram, the client browser will:
1. Make a request to http://coreRazor/asyncPoster . The request will be of type GET and will result in the Razor Page executing on the server and sending back some HTML markup to the browser to render.
Once the page is displayed, the user will proceed and enter some text in the text area control and press the 'Echo Text' button. At this point the interesting stuff starts to happen.
OPTIONS /api/EchoCors HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36
Access-Control-Request-Headers: content-type Accept: */*
Accept-Encoding: gzip, deflate
In order to remedy this, we need to modify the IIS settings, for the backend http://coreWebAPI site to allow anonymous requests in certina cases. This is where a good understanding of the HTTP pipeline in IIS comes in handly, and I would suggest a detour to video number 2 in the IIS Architecture and Components videos I have posted online a while ago: http://linqto.me/n/IISArchitecture . The HTTP processing pipeline is based on events being raised and pieces of code called 'http modules' subscribing to the notificatins sent out on these events to perform changes to the request or the response.
Two pipeline stages are important here: 1) Authentication and 2) Authorization. In the authentication stage, we are trying to understant who is the user behind the request. So we may chose to say the user is the 'anonymous user', and that annonymous authentication should also be turned on. Below are the authentication settings on IIS for the http://coreWebAPI site – note that both Windows Integrated Authentication and Anonymous Authentication are enabled:
In the next stage of request processing, the Authorization stage (or Authorization event), we need to make sure we only allow the anonymous requests from CORS preflight and require all other incoming requests have authentication credentials supplied. We can achieve this through Authorization Rules. A default authorization rule granting all users access to the site is already in place and supplied by default by IIS. We will start by modifying this rule to only allow anonymous users, if they send requests that are using the OPTIONS http verb. Below is the target configuration in IIS for this authorization rule:
However, this authorization rule is not enough as it will block all other requests. We need a second 'Allow' Authorization rule that needs to be introduced. This rule will perimit access to the application to a desired group of users – for the demonstration, I have chosen the 'Users' group, which should include most authenticated user accounts from the server where the backend application is hosted. In a real world scenario, we would configure an Active Directory group, such as 'Domain Users' if a really large scope was targetted, or some more restrained, like a specific user group.
Here is what the web.config file for the backend website will contain following the modifications to the authorization rules:
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
<aspNetCore processPath="dotnet" arguments=".\WebApiEcho.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
<remove users="*" roles="" verbs="" />
<add accessType="Allow" users="?" verbs="OPTIONS" />
<add accessType="Allow" users="" roles="USERS" />
These two rules will ensure that: CORS preflight OPTION request rules will be honored without requiring authenticatin, will all other incoming requests will need to present valid credentials to be allowed through to our ASP.net Core WebAPI application.
If we review the response headers for the CORS preflight request shown earlier, we can see that the server does authorize the receipt of CORS request from the http://coreRazor website:
HTTP/1.1 200 OK
By Paul Cociuba http://linqto.me/about/pcociuba