Click here to Skip to main content
15,896,359 members
Articles / Web Development / ASP.NET

ASP.NET Web API, CORS Authentication, SSL, and self signing certificate

Rate me:
Please Sign up or sign in to vote.
4.91/5 (9 votes)
15 Oct 2013CPOL3 min read 43.3K   20   1
How to enable CORS Authentication from ASP.NET Web API in development environments.

Introduction

I wanted to share some of my insight on setting up a development environment for asp .net Web API development in CORS domain situation. Our company started to develop a new SaaS solution for project management and this was the first time that the team worked outside of a closed enterprise environment. We approached a designer to develop our user interface for the product, and when we discussed the final details of the API, he requested that we implement CORS support for it. For him it was the first time developing UX against asp .net api, so he help us on implementing CORS. We researched the web for a solution and found the new preview version of ASP.NET that has a built-in CORS support. We replaced our referenced libraries with the new ones and added the needed configuration (we later reverted this change and returned to the stable version). It seemed like we had done our part. We then told our designer to check the API, and he couldn't make it work. He insisted that our problem was with CORS support. So we started to investigate the subject. After 3 days of searching and reading every piece of information available on the Web, we came up with these 3 steps for solving the problem.

Background - CORS

Cross-origin resource sharing - CORS - is a mechanism that allows JavaScript on a web page to make XMLHttpRequests to another domain, not the domain the JavaScript originated from. Such "cross-domain" requests would otherwise be forbidden by web browsers, per the same origin security policy. CORS defines a way in which the browser and the server can interact to determine whether or not to allow the cross-origin request.

Selfsigned certificate

Don't use the default, server-issued one unless you don't have any other option!!!

The first step of our solution was to get a trusted certificate issued from a digital certificate provider like Comodo, GeoTrust, VeriSign or other. There is also an option to get the certificate form an SSL Certificate reseller like namecheap.com or cheapssls.com that issue certificates at a lower price. Because we needed to use this certificate for development purposes, we got a Class 1 free certificate from startssl.com for free that is valid for one year. The other restriction for the certificate is that its key will be 2048 bits or more.

IIS Configuration

The second step we needed was to stop some browsers’ habit of sending the server the user's client certificate for two way authentication.

This task is simply done inside the Internet Information Services (IIS) Management Console. You need to do the following two steps on every path of your web directory from your API directory to your root: Go to IIS Manager and double click on SSL Settings.

Tenoresoftware iis fix1

Select Ignore under Client certificate; make sure that the Require SSL checkbox is selected.

Tenoresoftware iis fix2

These steps take care of the select client certificate prompt on all browsers that block the clients’ AJAX calls.

Delegating Handler

The final step in the solution is to handle the CORS HTTP request and the "pre-flight" OPTIONS request that is being sent from the browsers. This is the delegating message handler that we eventually implemented in our API. It's based mainly on Carlos Figueira’s article, as well as a few others found online. This handler, together with the signed certificate and the IIS configuration, is what finally enabled us to get our API working in cross origin situations over SSL.

C#
//
// Our Final Cors Message Handler Implementation:
//
public class CorsDelegatingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
      HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
    {
        const string origin = "Origin";
        const string accessControlRequestMethod = "Access-Control-Request-Method";
        const string accessControlRequestHeaders = "Access-Control-Request-Headers";
        const string accessControlAllowOrigin = "Access-Control-Allow-Origin";
        const string accessControlAllowMethods = "Access-Control-Allow-Methods";
        const string accessControlAllowHeaders = "Access-Control-Allow-Headers";

        bool isCorsRequest = request.Headers.Contains(origin);
        bool isPreflightRequest = request.Method == HttpMethod.Options;

        if (isCorsRequest)
        {
            if (isPreflightRequest)
            {
                return Task.Factory.StartNew(() =>
                {
                    var response = new HttpResponseMessage(HttpStatusCode.OK);
                    response.Headers.Add(accessControlAllowOrigin, request.Headers.GetValues(origin).First());

                    string controlRequestMethod =
                    request.Headers.GetValues(accessControlRequestMethod).FirstOrDefault();

                    if (controlRequestMethod != null)
                    {
                        response.Headers.Add(accessControlAllowMethods, controlRequestMethod);
                    }

                    string requestedHeaders = 
                      string.Join(", ", request.Headers.GetValues(accessControlRequestHeaders));

                    if (!string.IsNullOrEmpty(requestedHeaders))
                    {
                        response.Headers.Add(accessControlAllowHeaders, requestedHeaders);
                    }

                    return response;

                }, cancellationToken);
            }
            return base.SendAsync(request, cancellationToken).ContinueWith(t =>
                   {
                       HttpResponseMessage response = t.Result;
                       response.Headers.Add(accessControlAllowOrigin, 
                                request.Headers.GetValues(origin).First());
                       return response;
                   });
        }
        return base.SendAsync(request, cancellationToken);
    }
}

Remember to add the new delegating message handler to the MessageHandlers list in your Web API Configuration. Your Global.asax.cs should look similar to this:

C#
public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        WebApiConfig.Register(GlobalConfiguration.Configuration);
        WebApiConfig.ConfigureApi(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsDelegatingHandler());
    }
}

Points of Interest

  • Carlos Figueira's article on enabling CORS: link.
  • Chromium Issue 141839.
  • Enabling Cross-Origin Requests in ASP.NET Web API (pre-release): link.
  • Enable Cors resource sharing: link.
  • Wiki article on CORS: link.

History

  • 13 October, 2013: Version 1.0 - First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Tenore Ltd.
Israel Israel
I'm a lead developer and development manager at Tenore Ltd.

Worked for 6 year as MS Infrastructure and team leader.

Comments and Discussions

 
-- No messages could be retrieved (timeout) --