Mike_G
Mike_G

Reputation: 16502

Safari fails CORS

I am using a DynamicPolicyProviderFactory to determine if a request from a domain is allowed or not in an Asp.Net Web API 2.2 application. In order make that determination I am using a couple of RegEx patterns defined in the Web.Config

 <applicationSettings>
<MyApp.Properties.Settings>
  <setting name="AllowedDomains" serializeAs="Xml">
    <value>
      <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <string>http://localhost</string>
        <string>http*://*.domain1.com</string>
        <string>http*://*.domain2.com</string>
      </ArrayOfString>
    </value>
  </setting>
</MyApp.Properties.Settings>
</applicationSettings>

Then within my DynamicPolicyProviderFactory I create a RegEx I can use:

 //constructor
 public DynamicPolicyProviderFactory(IEnumerable allowedOrigins)//allowedDrigins is the strings passed from the config        {
        _allowed = new HashSet<Regex>();

        foreach (string pattern in allowedOrigins.Cast<string>()
            .Select(Regex.Escape)
            .Select(pattern => pattern.Replace("*", "w*")))
        {
            _allowed.Add(new Regex(pattern, RegexOptions.IgnoreCase));
        }

        if (_allowed.Count > 0)
            return;

        //if nothing is specified, we assume everything is.
        _allowed.Add(new Regex(@"https://\w*", RegexOptions.IgnoreCase));
        _allowed.Add(new Regex(@"http://\w*", RegexOptions.IgnoreCase));
    }

 public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        var route = request.GetRouteData();
        var controller = (string)route.Values["controller"];
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;
        var policy = GetPolicyForControllerAndOrigin(controller, originRequested);
        return new CustomPolicyProvider(policy);
    }

    //this is where the magic happens
    private CorsPolicy GetPolicyForControllerAndOrigin(string controller, string originRequested)
    {
        // Do lookup to determine if the controller is allowed for
        // the origin and create CorsPolicy if it is (otherwise return null)

        if (_allowed.All(a => !a.Match(originRequested).Success))
            return null;

        var policy = new CorsPolicy();

        policy.Headers.Add("accept");
        policy.Headers.Add("content-type");

        policy.Origins.Add(originRequested);
        policy.Methods.Add("GET");
        policy.Methods.Add("POST");
        policy.Methods.Add("PUT");
        policy.Methods.Add("DELETE");
        policy.Methods.Add("OPTIONS");

        return policy;
    }

Assuming the request origin is: http://sub.domain1.com and the script location is http://sub.domain1.com:8080. This works perfectly in Chrome, IE, FireFox, and Opera, but it fails in Safari. What is strange is that the error Safari gives in the browser console is

Failed to load resource: Origin http://sub.domain1.com is not allowed by Access-Control-Allow-Origin

What could be happening?

Another oddity I observed is that if both my origin and script location are http:sub.domain1.com:8080 CORS request are made by Safari (and pass) but other browsers correctly treat this as same origin.

edit: I have found that if I put a breakpoint where GetPolicyForControllerAndOrigin returns null, it is never hit and a policy is created and returned properly.

edit 2: I inspected the response from the server to the OPTIONS request by Safari and have found that the headers Access-Control-Allow-Headers and Access-Control-Allow-Origin headers are missing from the response. Also, on the request side, Safari is not adding the Host header. Is that required? Is that why it is failing?

Upvotes: 0

Views: 517

Answers (1)

Mike_G
Mike_G

Reputation: 16502

Issue was with the OPTIONS request by Safari. I was only allowing accept and content-type for allowed headers, but Safari was asking for origin as well.

Upvotes: 2

Related Questions