I’ve recently decided to embrace React for my front-end development, having previously used Sencha Ext JS for many years, but returning to jQuery this past year. The idea of using npm and webpack to build a web site once gave me the heebie-jeebies, but without it, you end up with the sprawling and unmanageable mess that I created with jQuery, which I did in haste to move away from Ext JS for a particular project. I still like Ext JS but, following some hefty price increases and uncertainty following a change of ownership, the wisdom of an exit strategy became apparent.
So, now that I’ve chosen React, how do I get the webpack-dev-server running on port 3000 to play nice with my existing .NET web service running in IIS on port 80? I searched but couldn’t find a way to get my React app — with hot module replacement — running within IIS. I once inherited a VB.Net web app (urgh!) that took 30 seconds to rebuild every time I had to test a change in the browser, and I wasn’t going to go through that nightmare again with “react-scripts build” (whose output, of course, does play nicely with my existing API when running from within IIS).
Instead of trying to get the webpack-dev-server (via “react-scripts start”) to talk to a “foreign” API (on a different port), why not try and approach things from the other end?
The easiest way to make my c# .NET WCF web service play nicely with React on port 3000 is to:
- Enable CORS
- Use identity impersonation
- Enable anonymous access
This way, I can take advantage of the hot module replacement updating my browser instantly whilst still using my API with its need for windows authentication retained (via impersonation). Here’s what you need to do…
Add the following three lines (in bold) to the web.config file at the root of your web service:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.web> <identity configSource="IdentitySecrets.config" /> </system.web> <system.webServer> <security> <authentication> <anonymousAuthentication enabled="true" /> </authentication> </security> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="http://localhost:3000" /> </customHeaders> </httpProtocol> </system.webServer> </configuration>
If you’re happy to keep your password as plain text in the main web.config file, you could just use this line instead of the one above:
<identity impersonate="true" password="whatever" userName="domain\me" />
But because I don’t like that idea, I’m shifting the configuration of the identity impersonation to a separate file, firstly so that I can encrypt it without cluttering up my main web.config, but secondly so that I can exclude it from version control. In order to encrypt the identity tag, create the following file in a temporary directory and call it web.config:
<configuration> <system.web> <identity impersonate="true" password="whatever" userName="domain\me" /> </system.web> </configuration>
Now open a command-prompt as Administrator and navigate to that directory. Find the appropriate version of .NET for your web service then execute the following command to encrypt the file (the period at the end is important; it denotes the current directory where the web.config file is located):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\apsnet_regiis.exe -pef system.web/identity .
If you encounter any problems trying to encrypt the file, see if this article is of any help. You should now have an encrypted version of the web.config file, which will look similar to the picture below:
You will need to open the newly-encrypted web.config file in a text editor and remove the <configuration> and <system.web> tags, since the
configSource attribute on the <identity> tag expects an <identity> tag to be the root element in the file (but aspnet_regiis.exe won’t encrypt it without the file looking like a proper web.config). After removing those tags, save it as IdentitySecrets.config in the same location as the web.config of your web service.
With identity impersonation and CORS now configured in your web service, you should be good to go. Rebuild the web service and then make a call to
http://localhost/myapi from your
http://localhost:3000 React app, and it should work nicely.
The only tiny problem with this approach is that you will get errors like this when navigating into the authentication node of the web service in the IIS Admin console:
When you dismiss the error message, the authentication section looks like this, with “Retrieving status…” in the status column for all authentication types:
The web service continues to operate just fine, but the admin console can’t handle the encrypted identity impersonation. If you need to make other changes to the authentication settings through the console, just remove the <identity> tag from the web.config, make the changes, then put the <identity> tag back when you’re done.
One last thing that you should probably do is make some changes to your web.config transformation files (if you have them) so that the identity impersonation, anonymous access, and CORS changes are not deployed to your test and production servers (which will use the built version of your React app):
<?xml version="1.0"?> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <system.web> <compilation xdt:Transform="RemoveAttributes(debug)" /> <identity xdt:Transform="Remove" /> </system.web> <system.webServer>
/></authentication> </security> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" xdt:Locator="Match(name)" xdt:Transform="Remove" /> </customHeaders> </httpProtocol> </system.webServer> </configuration>
If you’re in a team of developers and they don’t wish to use identity impersonation in their development copies of the web service (and also don’t want to keep commenting out the <identity> tag each time they do a
git pull or
svn update), they can just create an IdentitySecrets.config file as follows:
<identity impersonate="false" password="whatever" userName="nobody" />
Enabling anonymous authentication (so that React can access the API unhindered) may also mean you have to change the way you’re identifying users in your web service. Instead of using
System.Web.HttpContext.Current.User I had to change to using
System.Security.Principal.WindowsIdentity.GetCurrent() (which seems to identify the current user correctly, whether I’m using anonymous+impersonation or Windows authentication by itself).