Geeks With Blogs

News

Currently Reading

CLR via C#m
Under The Influence(of code) Abhijeet Patel's blog

If you are working with WebAPIs, you inevitably will have to deal with CORS.
This is a good read for the uninitiated.

I won't go into the specifics of how browsers issue, and servers handle (or should handle) CORS, but at a high level, the following sequence of events occur when you need to update/create a resource from a domain other than the one your app originates from.

  • Client web app from domain "foo.com" issues a POST/PUT/PATCH to a resource on domain "bar.com"
  • The browser needs to know whether the server can handle said request so it sends a precursor request to the actual request, know as a "pre-flight" which uses the OPTIONS verb to the same endpoint. The CORS spec says that OPTIONS requests need not include any auth credentials.
  • The server typically responds with HTTP 200 and a bunch of response headers letting the browser know that it's willing to serve the "actual" request. If the server responds with any failure HTTP status code, the "true" request and payload are never sent to the server and the operation is deemed as failed.
  • If a 200 is received with the necessary response headers, the browser then sends the "true request" to the end point and the server handles the operation accordingly.

If you run the debugger tools on most modern browsers, you will see the above sequence of events and requests being issued. IE tends to be a bit different, in that, it uses an XDomain object instead XHR(XmlHttpRequest) compared to browsers like Chrome and FF which use XHR. Go figure!

This subtle difference in how browsers issue cross domain requests is a major PITA when your WebAPIs are secured with some authentication scheme.You do secure your WebAPIs don't you!

The simplest auth scheme and also the most common one used in an intranet scenario with IIS is WindowsAuthentication using NTLM/Kerberos.

Since IIS doesn't allow for conditionally enabling/disabling Windows Auth it's an all or nothing deal. This means that OPTIONS requests are outright rejected by IIS before even hitting application code since the request won't contain any auth information. Now, you could enable anonymous auth but this won't cut it either since the request still flows through the IIS pipeline and will inevitably hit the windows auth module and you inevitable get a 401.x response. Secondly, most right minded developers would not feel comfortable enabling Anonymous auth and the security policy in most enterprises won't even allow this. So what do you do?

If you are running on WebAPI 2.x you could install Microsoft.AspNet.WebApi.Cors

Basically, this installs a DelegatingHandler to handle the HttpRequestMessage in the ASPNET pipeline before it hits your controller/actions/filters etc.
Folks have had varying levels of success with this package.
Personally, I've not been able to get this to work on WebAPI 2.x with Windows Auth and if you have any
WebAPI 1.x based code, this package is not an option since it applies only to WebAPI 2.x

Another option is to add the following response headers to the Web.config, these
headers are expected in response to a pre-flight request.

<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Credentials" value="true"/>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD, PATCH" />
<add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, content-type, Accept" />
<add name="Access-Control-Max-Age" value="1728000"/>
</customHeaders>
</httpProtocol>
</system.webServer>

While this does provide the necessary response headers that XHR expects for the
pre-flights to succeed, it does not account for windows authentication which kicks
in and rejects the request with a 401 Unauthorized error!

Moreover, I'm not a big fan of using "*" for the origin, leaving it wide open, and neither am
I fond of specifying every single domain that I am willing to play ball with.

The only way to have your cake and eat it too is to roll up your sleeves and write
a HTTP module and handler which plugs into the IIS pipeline, giving you a chance
to inspect the request and take the appropriate course of action before authentication
kicks in.

namespace WebAPI.Infrastructure
{
    using System;
    using System.Web;
    using System.Collections;
    using System.Net;
    public class CrossOriginModule : IHttpModule
    {
        public String ModuleName
        {
            get { return "CrossOriginModule"; }
        }

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;
            HttpContext context = application.Context;
            CrossOriginHandler.AddCorsResponseHeaders(context);
        }

        public void Dispose()
        {
        }
    }

    public class CrossOriginHandler : IHttpHandler
    {
        #region Data Members
        const string OPTIONS = "OPTIONS";
        const string PUT = "PUT";
        const string POST = "POST";
        const string PATCH = "PATCH";
        static string[] AllowedVerbs = new[] { OPTIONS, PUT, POST, PATCH };
        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";
        const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
        const string AccessControlMaxAge = "Access-Control-Max-Age";
        const string MaxAge = "86400";
        #endregion

        #region IHttpHandler Members
        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            switch (context.Request.HttpMethod.ToUpper())
            {
                //Cross-Origin preflight request
                case OPTIONS:
                    AddCorsResponseHeaders(context);
                    break;

                default:
                    break;
            }
        }
        #endregion

        #region Static Methods
        public static void AddCorsResponseHeaders(HttpContext context)
        {
            if (Array.Exists(AllowedVerbs, av => string.Compare(context.Request.HttpMethod, av, true) == 0))
            {
                var request = context.Request;
                var response = context.Response;
                var originArray = request.Headers.GetValues(Origin);
                var accessControlRequestMethodArray = request.Headers.GetValues(AccessControlRequestMethod);
                var accessControlRequestHeadersArray = request.Headers.GetValues(AccessControlRequestHeaders);
                if (originArray != null &&
                    originArray.Length > 0)
                    response.AddHeader(AccessControlAllowOrigin, originArray[0]);
                response.AddHeader(AccessControlAllowCredentials, bool.TrueString.ToLower());

                if (accessControlRequestMethodArray != null &&
                    accessControlRequestMethodArray.Length > 0)
                {
                    string accessControlRequestMethod = accessControlRequestMethodArray[0];
                    if (!string.IsNullOrEmpty(accessControlRequestMethod))
                    {
                        response.AddHeader(AccessControlAllowMethods, accessControlRequestMethod);
                    }
                }
                if (accessControlRequestHeadersArray != null &&
                    accessControlRequestHeadersArray.Length > 0)
                {
                    string requestedHeaders = string.Join(", ", accessControlRequestHeadersArray);
                    if (!string.IsNullOrEmpty(requestedHeaders))
                    {
                        response.AddHeader(AccessControlAllowHeaders, requestedHeaders);
                    }
                }
            }
            if (context.Request.HttpMethod == OPTIONS)
            {
                context.Response.AddHeader(AccessControlMaxAge, MaxAge);
                context.Response.StatusCode = (int)HttpStatusCode.OK;
                context.Response.End();
            }
        } 
        #endregion
    }
}

We include the expected response headers in AddCorsResponseHeaders.
This is done by echoing back the header values from the request headers for the "Origin"
and the allowed verbs(methods).


This tells the browser that the server is willing to serve the request from
the calling domain (so no *) and is able to handle the requested
verb (PUT/POST/PATCH etc.)

For OPTIONS,we effectively short circuit the request pipeline, which is the
"pre-flight" that most browsers will issue and respond with a 200 OK.
The key here is to end the request processing via: context.Response.End();
This short circuits the request pipeline and stops any downstream authentication
modules from runnning.

The module can be installed by simply adding to Web.config like so:

<system.webServer>  
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="CrossOriginModule" preCondition="managedHandler" type="WebAPI.Infrastructure.CrossOriginModule, assemblyname" />
    </modules>
    <handlers>
      <remove name="WebDAV"/>
      <remove name="OPTIONSVerbHandler"/>
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." 
           verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." 
           verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." 
           verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
      <add name="CrossOrigin" verb="OPTIONS" path="*" type="WebAPI.Infrastructure.CrossOriginHandler, assemblyname" />
    </handlers>   
     <security>
       <authorization>
         <remove users="*" roles="" verbs=""/>
         <add accessType="Allow" users="*" verbs="GET,HEAD,POST,PUT,PATCH,DELETE,DEBUG"/>
       </authorization>
     <requestFiltering>
       <requestLimits maxAllowedContentLength="6000"/>
       <verbs>
         <remove verb="OPTIONS"/>
         <remove verb="PUT"/>
         <remove verb="PATCH"/>
         <remove verb="POST"/>         
         <remove verb="DELETE"/>
       </verbs>
     </requestFiltering>
   </security> 
  </system.webServer>

Points to note:
  • The WebDAVModule has been removed
  • The WebDAV and OPTIONSVerbHandler have been removed
  • ExtensionlessUrlHandlers include the verbs used for updates (POST/PATCH etc)
  • The requestfiltering section ensures that the necessary verbs are allowed.
    This is key to ensuring IIS does not restrict the verbs of interest.

The above can all be wrapped up nicely in a Nuget package and installed in any
WebAPI or ASPNET project that needs support for CORS.


This is precisely what I'll do next and hopefully can host it on Nuget.
In the meantime, happy coding!

Posted on Saturday, June 4, 2016 10:01 PM ASP.NET , iis , webapi , CORS | Back to top


Comments on this post: Adding CORS support for ASP.NET / WebAPI: The no hassle way

# email database
Requesting Gravatar...
If you want to do bulk email marketing campaigns you will use our Email lists for create a bulk email marketing campaigns for your company. Latest database provide you opt in email lists and latest Email lists.
see mote...http://www.latestdatabase.com
Left by alr razzak on Jun 08, 2016 10:59 PM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
With both FRS and GMRS and an auto channel scan function, it will automatically scan the clearest channel open for you to use.
192.168.1.1
Left by llection on Jul 29, 2016 3:11 AM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
Thanks for providing the info to adding CORS for ASP.net with no hassle. Music Paradise pro is my favorite music app to enjoy the music with latest collections. Install Music paradise pro windows to listen to the music for free.
Left by Music paradise pro on Dec 03, 2016 6:18 AM

# Obat Herbal Gagal Ginjal Kronis
Requesting Gravatar...
That is my first time see here. From the tons of comments on your articles, I imagine I'm not just one having all the enjoyment in this article! Obat Herbal Gagal Ginjal Kronis
Left by tukinem on Jan 12, 2017 10:16 PM

# Obat Herbal Jantung Bengkak dan Bocor
Requesting Gravatar...
I just couldn't leave your website before telling you that I truly enjoyed the best quality details you present for your visitors? Will be back again frequently to check up on brand new publish. Obat Herbal Jantung Bengkak dan Bocor
Left by parijah on Jan 12, 2017 10:17 PM

# Obat Herbal Jantung Koroner Alami
Requesting Gravatar...
Your publish had provided me with another point of view on this topic. I had no strategy that things can possibly work on this form as well. Thank you for sharing your perspective. Obat Herbal Jantung Koroner Alami
Left by suparno on Jan 12, 2017 10:18 PM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
Super solution. Thanks a lot.
Left by Wanderer on Jan 13, 2017 10:56 AM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
If you want to download books then use torrents. Torrent are very popular and easy to get your favorite stuff for free. check it out for torrent sites for free books.
Left by Jane on Jan 14, 2017 5:55 AM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
Thanks for providing the info to adding CORS for ASP.net with no hassle. Music Paradise pro is my favorite music app to enjoy the music with latest collections. Install Music paradise pro windows to listen to the music for free
delherbal.com
Left by delherbal on Apr 24, 2017 9:19 PM

# re: Adding CORS support for ASP.NET / WebAPI: The no hassle way
Requesting Gravatar...
Music Paradise Pro is an application which lets its users download music for free. We can listen all my new track here. And I never tried it gonna try it Thank you Anita Paul
https://techvy.com/music-paradise-pro-downloader/
Left by Anita Paul on Oct 07, 2017 12:11 AM

Your comment:
 (will show your gravatar)


Copyright © Abhijeet Patel | Powered by: GeeksWithBlogs.net