Here is the scenario:
- .NET Web Api running on an IIS Server (8.5 – although the problem was recreated on 7.5 too)
- v4.0 Integrated Application Pool with a domain account identity.
- Only Windows Authentication is enabled. No anonymous access is allowed.
- Making requests to the Web Api works in the following configuration:
- Using a different domain account to the one used by the Application Pool AND
- From a different machine and using a browser or the Invoke-RestMethod PowerShell command
- Making requests to the Web Api does not work in the this configuration and results in a 401:Unauthorized error being displayed.
- Using a different domain account from the same server OR
- Using the same domain account to the one used by the Application Pool AND
- From either the same or different machine, using a browser or the Invole-RestMethod PowerShell command
The problem was that a Schedueld Job, created as a PowerShell script, was being run from the same server and the Invoke-RestMethod was generating the 401:Unauthorized error.
The solution was to set the relevant Service Principle Names for the IIS Server, but instead of doing it for the specific domain user account it was configured for the server. This included SPN’s for the computers NetBIOS name and the FQDN as well as the host name of the Web Api.
This post just covers a few useful things I have found while I have been developing an ASP.NET Web API that uses AttributeRouting for Web API and ELMAH for Web API.
I found AttributeRouting much easier to configure and administer than using MapHttpRoute in the WebApiConfig.cs file; especially using the RoutePrefix at the controller level. Running tests on the attributes using the Route.axd was also very straight forward. I also found Fiddler (v188.8.131.52) to be very helpful in throwing different unit tests at my newly created web API. This is simple and powerful configuration that works straight out of the box.
Using the Elmah.Contrib.WebApi took a little more configuring to get working but I found a good place to start, after you had installed the NuGet packge was on the sample web.config page. This provides detailed examples of the configuration settings needed for the different types of log stores as well the basic settings for the different flavours of IIS. There were a couple of additional tweaks I would recommend:
- Since I am not generating any View I removed the
filters.Add(new HandleErrorAttribute()); in the FilterConfig. The HandleErrorAttribute to serve up a View called Error. Since this is a Web API project and I don’t have any Views then it wasn’t needed.
- Instead, there is a rather neat process for capturing unhandled errors that I found here.
- Finally, if you are using a simple XML file to store your errors you need to make sure that the security configuration is correct for the folder where the files will be generated and stored. For example, I had my files stored in the App_Data folder under the root of the web site on IIS 7.5. The application pool for this site was running under the ApplicationPoolIdentity account and so this account needed to have write permissions to the App_Data folder. Instructions for doing this can be found here under the Securing Resources section of the article.
One of the issues I have encountered whilst developing MVC apps that require Windows Authentication was the problem that it keeps wanting to redirect to the login page when there isn’t one. The problem manifested itself when I was using IIS to Browse to my application running as a virtual directory under my default web site; it kept wanting to redirect to ‘~/Index’ and since my default route was not configured to find an Index Controller it would return a Page Not Found error.
After doing some digging I realised that the web.config file contained the following entry:
Replacing the loginUrl attribute with “~/Home/Index” solved the problem as it matched my default Route; but I knew that I wasn’t using forms authentication and had no login page. After doing some more research I came across a few sites which recommended adding the following keys to the <appSettings> section:
<add key="autoFormsAuthentication" value="false" />
<add key="enableSimpleMembership" value="false"/>
Having made the additions and removed the <forms> tag the site functioned as expected and the requests for the default route were handled correctly.
This one was frustrating to resolve as there are a number of reasons this error could occur and lots of information out there about fixing the problem. My scenario was this; my IIS 7.5 web server was throwing this error when trying to connect to a SQL Server database on a different machine. I would get this error when calling the web application from a third machine. One of the frustrations was that if I made the same request from my browser on the actual web server I did not get any error, just the page I was expecting.
After searching the net, trying a number of the suggested solutions, triple checking my connectionString entry in the web.config I remembered that I hadn’t tried using the IP Address in the connectionString rather than the server name. Making this change solved my problem and my web application worked as expected from any machine. As it turns out, supplying the FQDN (Fully Qualified Domain Name) in the connectionString also worked. So now I had to try and figure out why the the web server was having trouble resolving to this particular database server?
Running the TRACRT command-line against my database server name gave me an unexpected response; the name of another server. So that got me thinking about whether the database server had been renamed recently – it was a VM that I was not responsible for maintaining. So I opened up SSMS and ran the command SELECT @@SERVERNAME AS ‘Server Name’. This gave me the name different name altogether! If I put this third server name into the connectionString that also worked, without needing to use the FQDN.
Some further investigation later revealed that the VM was a copy of another VM which was then renamed twice but no one had followed the procedure for renaming the SQL Server instance. This was what was contributing to the problems with the server name being resolved correctly.
Its a well known fact that Using HTTP Compression for Faster Downloads makes good sense. Also, if you have any problems getting this to work then Troubleshooting HTTP Compression in IIS6 is a good place to start.
This is just a little ‘note to self’ about using the IIS Metabase Explorer to configure your web server for compression, specifically at a site level. My situation arose because the IT guys didn’t want to apply compression across every site on the server, if it wasn’t necessary. I was using the Metabase Explorer to change my IIS settings and having configure the HcDoDynamicCompression, HcDoDynamicCompressionLevel and HcScriptFileExtensions records for defalte and gzip keys, I went to set the DoDynamicCompression record for the site.
After creating the DoDynamicCompression record on the root node for the site and changed the value to 1 (true) I went to check the response from the server. To my surprise the response still wasn’t being compressed. After a bit of head scratching I deleted the DoDynamicCompression record and then re-added it, this time using the adsutil IIS admin script. After checking the response and finding it was now compressed successfully I went back to the Metabase Explorer to see what was different.
For site level compression there are a couple of additional values that need setting for the DoDynamicCompression record:
- The User Type needs to be changed to File.
- The Attributes need to be set to Inheritable.
I have been using IIS Express to do some of my recent development instead of using the ASP.NET Development Server so that I could use the additional functionality within IIS. (Personally, I hate developing with the ASP.NET Development server but I am having to develop on XP at the moment and IIS 5.1 is not much better. Getting IIS Express has been a real bonus.). I had configured my IIS site originally with a localhost address and Visual Studio debugging worked fine. Unfortunately, Fiddler doesn’t like monitoring localhost addresses so I created a new entry in my Hosts file to use localhost.
This created my second issue. When I tried to run my app within VS I got the ‘Unable to start debugging on the Web server’ error, IISExpress was reporting 401 errors and all because of the entry in the Hosts file. So after the usual search to try and find similar problems I came across this MSDN KB article. I actually used Method 2 in the article and I didn’t need to reboot my machine, just restart my IISExpress instance. This solved my problem as Visual Studio was then happy to go through the authentication process and Fiddler was capturing my Requests/Responses.
If you suffer the pain of using the ASP.NET Development Server (Cassini) and would like an alternative take a look at this post. The full download is available here.