Wednesday, October 4, 2017

Cross-Origin Request Blocked (CORS)

To speed up the development and future upgrade, we split the huge application into multiple AJAX services. Each AJAX service in running in it's own application pool and it can be run on different server. The design works perfectly. But, when you want to consume the AJAX services through the browser, you bang your head: "Cross-Origin Request Blocked".

This is the error message that appeared in the Firefox:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/schedule/q?code=tx&ts=1507099862873. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘(null)’).

Google Chrome returned an error message that is slightly different:

Failed to load http://localhost/schedule/q?code=tx&ts=1507099946004: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed. Origin 'http://localhost:56269' is therefore not allowed access.

Now, if you are googling for the solution, you will end up with add the following settings in the web.config.

  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
    </customHeaders>
  </httpProtocol>

But, the wild card origin is no longer supported. You ended up with adding the specific origin.

  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="http://localhost:56292" />
      <add name="Access-Control-Allow-Headers" value="Content-Type" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
    </customHeaders>
  </httpProtocol>

Imagine that you are hosting your AJAX services in multiple servers with different sub-domains.... the above solution will not work. This is because you are not allowed adding more than one domain name to "Access-Control-Allow-Origin".

To solve the problem, we need to handle the OPTIONS verb by adding the following settings in the web.config:

  <system.webServer>
    <handlers>
      <add verb="OPTIONS" name="check_opt" path="*" type="ajaxLib.CORS_OPTIONS" />
    </handlers>
  </system.webServer>

And below is the simplified code that allows CORS:

namespace ajaxLib {
public class CORS_OPTIONS : IHttpHandler
{
  public void ProcessRequest(HttpContext context)
  {
        if (context.Request.HttpMethod.ToUpper() == "OPTIONS") {
           string s = context.Request.Headers["Origin"];

           if (!string.IsNullOrWhiteSpace(s))
           {
             context.Response.AppendHeader("Access-Control-Allow-Origin", s);
             context.Response.AppendHeader("Access-Control-Allow-Headers", "Content-Type");
             context.Response.AppendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
           }
    }
  }

  public bool IsReusable { get { return false; }}

}}

Two possibilities if you want to use the above code in the live environment,

1. If your service allows public access without any restriction, skip checking the Origin value.
2. If your service allows specific domain to access, you must check the Origin value before return it to the caller.