Developing in React with an API hosted in IIS

The Problem

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 my 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 Solution

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\aspnet_regiis.exe -pef system.web/identity .
Administrator command-prompt window

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:

Web.config file showing encrypted values after running aspnet_regiis.exe

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.

Minor Caveat

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:

IIS Admin console error message

When you dismiss the error message, the authentication section looks like this, with “Retrieving status…” in the status column for all authentication types:

IIS Admin console showing the Authentication section

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.

Finishing Up

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>
    <security>
        <authentication>
            <anonymousAuthentication xdt:Transform="Remove" />
        </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).

Enjoy.

2 thoughts on “Developing in React with an API hosted in IIS

  1. My blog must have thought it was spam since it contained URLs and blocked it. I’ve now approved it. To answer your question, you don’t want to call your API on port 3000 because it doesn’t run on port 3000 (the “npm start” web server), it should be running on port 80 (I presume). Also, try getting it to work first without encrypting the identity impersonation bit (that’s too much effort until you can get it to work plainly).

    Also, I didn’t set baseUrl in my React app because I didn’t want to mess with its own routings. All I’m doing is just calling localhost/api directly in my Axios requests within the app, no port 3000 anywhere in my Axios calls. And thanks for pointing out my type. It’s now fixed :-)

  2. I’ve been looking for a solution for this same problem and this idea sounds promising. However, it’s not working for me. When I use an encrypted identity section I get an error when I try to hit my API: “The requested page cannot be accessed because the related configuration data for the page is invalid.” When I put the credentials straight in the config I still get 401 unauthorized errors. My API requires windows auth.

    Also, where you say “make a call to http://localhost/myapi from your http://localhost:3000 React app, and it should work nicely”, should that say “make a call to http://localhost:3000/myapi?” I don’t know why it would drop the port.

    Secondly, I assume your API is hosted in local IIS. Did you set a baseUrl in your React app to point to that URL? The default is to use the same as the app, so it would still try to hit localhost:3000, which, unless you made other changes and are somehow hosting your API on the same port as your React app, I don’t see how this would work.

    Lastly, you have a typo in your aspnet_regiis command. You have apsnet instead of aspnet. Took me a good while to see that and realize why the command wasn’t working for me.

    Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *