23

I've searched thoroughly but cannot find a solution to this issue in my particular circumstance.

Cross-domain service calls using Fiddler (POST) execute correctly and the data is received. However, through the browser (Chrome) I am getting the message 'preflight has invalid HTTP status code 404'

I have a Web API application and have installed CORS and ensured the following is present in the web.config file:

<system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <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>
</system.webServer>

Here is the Ajax call:

var secretKey = 'difusod7899sdfiertwe08wepifdfsodifyosey',
    url = 'http://api.intrinsic.co.uk/api/v1/PTS/ActiveDrivers?api_key=098werolllfWnCbPGAuIXVOJidDHRfYcgxImMlxTXopuekXrSOqOWzEAIdeNTWGPQPpyHxgVGsFysGFKPzq';

  jQuery.ajax ({
      url: url,
      type: "POST",
      data: JSON.stringify({ secretKey: secretKey}),
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      success: function(data){
          var content = "<table class=\"container\"><thead><tr><th>Driver Number</th><th>Timestamp</th><th>VRN</th><th>Latitude</th><th>Longitude</th><th>Track Link</th></tr></thead><tbody>";
          $.each(data.ActiveDrivers.DriverLocationStatus, function (index, element) {
              content += "<tr><td>" + element.DriverNumber + "</td>";
              content += "<td>" + dateFormat(element.Timestamp, "d/m/yy") + " " + dateFormat(element.Timestamp, "h:MM TT") + "</td>";
              content += "<td>" + element.VRN + "</td>";
              content += "<td>" + element.CurrentLatitude + "</td>";
              content += "<td>" + element.CurrentLongitude + "</td>";
              content += "<td><a href=\"https://www.google.co.uk/maps/place//@" + element.CurrentLatitude + "," + element.CurrentLongitude + ",15z/\" target='_blank'>Track &raquo;</a></td></tr>";
          });
          content += "</tbody></table>";
          $( "#result" ).html( content );
      }
  });

Obviously, works on the same domain perfectly and, as mentioned, it works using Fiddler.

I'm certain it is the browser's preflight OPTIONS check that is failing for content-type of 'application/json' but I'm not sure how to fix it.

Is there something missing in the web.config file that I should add?

I have tried removing 'content-type' with no affect.

I had hoped this article would solve the issue (it seemed promising) but the same error is encountered:

XMLHttpRequest cannot load [URL]. Response for preflight has invalid HTTP status code 404
A-Sharabiani
  • 17,750
  • 17
  • 113
  • 128
ChrisCurrie
  • 1,589
  • 6
  • 15
  • 36

7 Answers7

38

Thanks but getting 405 error,after the above config changes.

Finally it works after adding below code in web api Global.asax file

protected void Application_BeginRequest(Object sender, EventArgs e)
    {
        //HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
        if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        {
            HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
            HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
            HttpContext.Current.Response.End();
        }
    }
Hussain
  • 449
  • 4
  • 4
15

I finally got this to work.

This article 'WebAPI with CORS – IIS Intercepts OPTIONS Verb' informed my thinking. An image showed where, in IIS, the OPTIONS handler mapping appeared and why, within the web.config we needed to remove it to ensure IIS did not intercept.

When I took a look at IIS that handler WAS NOT there. I then took a look at the linked article 'Can't set HttpHandler order using Web.Config unless a «clear» tag exists' and saw that, in this article, after removing the OPTION handler, it was then explicitly added within the web.config.

As I could not see the OPTION handler in IIS, I too added it to the web.config file and all suddenly worked. It appeared that this addition is what was needed.

The final web.config handlers section looks as follows (notice I decided to keep the initial 'remove' just in case this caused problems if I migrated to a different web server in the future).

<system.webServer>
    <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="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" requireAccess="None" responseBufferLimit="4194304" />
    </handlers>
    <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, OPTIONS" />
      </customHeaders>
    </httpProtocol>
</system.webServer>
ChrisCurrie
  • 1,589
  • 6
  • 15
  • 36
  • Could you please help me on this: https://magento.stackexchange.com/questions/170342/magento-htaccess-response-for-preflight-has-invalid-http-status-code-400 – Sushivam Apr 16 '17 at 05:45
  • Thanks worked. It is important to add the handlers as well. – Dileep Aug 14 '17 at 00:16
11

This worked for me.

In Global.asax

protected void Application_BeginRequest(Object sender, EventArgs e)
{
    //HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }
}

In Web.config

    <httpProtocol>
        <customHeaders>

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

rebuild and hey presto.

Darren Street
  • 1,652
  • 17
  • 21
2

I have a similar setup that was showing 404 errors and 500 errors as I was attempting to get CORS running on my web service. My fix basically used Hussain's solution, but as I cleaned up my fix, I noted that only one Response line was needed, and I was able to keep the original web handlers in the web.config, and did NOT need to move all of the response handlers into code.

Basically, my fix includes this ONE MAJOR FIX in my ApplicationOnBeginRequest handler:

    private void ApplicationOnBeginRequest( object sender, EventArgs eventArgs )
        {
...
            if ( context.Request.HttpMethod == "OPTIONS" )
                response.End();
        }

and these handlers in my web.config:

<system.webServer>
    <!--Other handlers/modules ...-->
    <httpProtocol>
        <customHeaders>
            <clear />
            <add name="Access-Control-Allow-Origin" value="*" />
            <add name="Access-Control-Allow-Credentials" value="true" />
            <add name="Access-Control-Allow-Headers" value="Content-Type,Accept" />
            <add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" />
        </customHeaders>
    </httpProtocol>
   </system.webServer>

Sorry I couldn't send this note as a comment to Hussain's answer.

1

For those using .NET Core 3.1, here is a COMPLETE solution (front-end to back-end):

My problem: When I enabled the windows authentication on my web API, I could not do fetch calls from my react app to my .NET Core 3.1 web API, CORS was freaking out. With Anonymous authentication it worked, but not when windows authentication is enabled.

1.launchSettings.json

this will be used only for your dev environnment, make sure windows auth is also enabled in IIS on your prod server.

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": false,
    "iisExpress": {
      "applicationUrl": "http://localhost:58747",
      "sslPort": 0
    }
  },
 {... more settings if any}
}

2.Startup.cs:

CORS policy is enabled here. The order of methods is important here. Also, you don't need to set those in a web.config

public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy", //give it the name you want
                           builder =>
                           {
                               builder.WithOrigins( "http://localhost:3000", //dev site
                                                    "production web site"
                                                   .AllowAnyHeader()
                                                   .AllowAnyMethod()
                                                   .AllowCredentials();
                           });
        });

        //database services here

        services.AddControllers();
    }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        // global policy same name as in the ConfigureServices()
        app.UseCors("CorsPolicy");

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

3.Controller(s):

using Microsoft.AspNetCore.Cors;
... your other usings

namespace ProjectTest.Controllers
{
    [ApiController]
    [EnableCors("CorsPolicy")] //THIS HERE needs to be the same name as set in your startup.cs
    [Route("[controller]")]
    public class FooController:Controller
    {
        [HttpGet("getTest")]
        public JsonResult GetTest()
        {
            return Json("bar");
        }
    }
}

4.React Component fetch call example:

The "credential: 'include'" is the secret

    await fetch('http://localhost:3000/Foo/getTest', {
        method: 'GET',
        credentials: 'include'
    }).then(resp => resp.json());
Homeslyce
  • 258
  • 3
  • 11
0

For asp core use this code in Startup.cs in Configure procedure. I used for 2.0 version but i think it should work with older too

app.UseCors(builder => {
                builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
            });
valentasm
  • 2,137
  • 23
  • 24
0

This helped me too, I had CORS configured in web.config already

protected void Application_BeginRequest(Object sender, EventArgs e)
{
    //HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();
    }
}