2


my environments:

  • backend (localhost) written in .net web api 2
    enabled package "Microsoft.AspNet.WebApi.Cors"
  • frontend (localhost) with vue-js-2 with webpack-simple
    vue-resource for http requests


I am experiencing some CORS issues which I cannot solve:
Launching "simple requests" with GET verb works, but when using POST verb I get CORS error, saying:

Access to XMLHttpRequest at 'http://localhost:59837/api/Employees' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I've read mozilla's CORS reference and noticed that indeed my POST request is sent as a preflight request first, with OPTIONS verb and Access-Control-Request-Method: POST header.

on my WebApiConfig file I have:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
           ...

            // Web API enable CORS
            System.Web.Http.Cors.EnableCorsAttribute cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
            config.EnableCors(cors);
        }
    }

and my POST request code is as follows:

testPOST(){
      const credentials = { username: '1', password: '11'};
      this.$http.post('http://localhost:59837/api/Employees', {params: {id: '2'}, body: credentials, method: 'POST', headers: {'Content-Type': 'text/plain'}})
      .then(function (response){
          console.log(response);
      })
      .catch(function (error) {
          console.log(error);
      });
    }

and the POST function in the controller:

public async Task<HttpResponseMessage> PostEmployees(Credentials creds)
        {

            Employees emp = await db.Employees.FindAsync(Int32.Parse(creds.username));

            if (emp.password.Equals(creds.password))
            {
                emp.password = "";
                return Request.CreateResponse(HttpStatusCode.OK, emp);
            }

            else
                return Request.CreateResponse(HttpStatusCode.Unauthorized, "Username or password are incorrect");            
        }

My thinking was that perhaps I need to define the POST request headers to the authorized "text/plain". Using Fiddler I found the outgoing request but it didn't have the text/plain header.. Right now, I am not even sure if my error is related to the backend configuration or to the frontend request sending method. Any one encountered anything similar? (sorry for all the code, I wanted to be as encompassing yet minimal as possible)

yoad w
  • 315
  • 3
  • 10
  • The error message cited in the request is unrelated to your POST handler. Your server never actually gets around to executing any of the code in your POST handler, because the browser never ends up sending the POST request from your frontend code. Instead after the preflight fails, the browser stops right there and never even tries the POST. So to prevent the preflight from failing, you need to look at how your server handles OPTIONS requests — you need to ensure that the server responds to OPTIONS with a 2OO OK success response and the appropriate Access-Control-Allow-\* headers. – sideshowbarker Dec 07 '18 at 08:35
  • This should help you: https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core/43303224#43303224 – Ric.H Dec 07 '18 at 12:41

2 Answers2

3

Web Api 2 doesn't respond to OPTIONS request. You can add Handler for it. Here it is how i solved it once. Create a IHttpModule somewhere. Here is mine:

namespace AAWebSmartHouse.WebApi.Infrastructure.Modules
{
    using System.Web;

    public class OPTIONSVerbHandlerModule : IHttpModule
    {
        public void Init(HttpApplication context)
        {
            context.BeginRequest += (sender, args) =>
            {
                var app = (HttpApplication)sender;

                if (app.Request.HttpMethod == "OPTIONS")
                {
                    app.Response.StatusCode = 200;
                    app.Response.AddHeader("Access-Control-Allow-Headers", "content-type,accept,authorization");
                    app.Response.AddHeader("Access-Control-Allow-Origin", "*");
                    app.Response.AddHeader("Access-Control-Allow-Credentials", "true");
                    app.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE");
                    app.Response.AddHeader("Content-Type", "application/json");
                    app.Response.AddHeader("Accept", "application/json");
                    app.Response.End();
                }
            };
        }

        public void Dispose()
        {
        }
    }
}

And add it in Web.config:

<modules>
  <!-- ... -->
  <add name="OPTIONSVerbHandlerModule" type="AAWebSmartHouse.WebApi.Infrastructure.Modules.OPTIONSVerbHandlerModule" />
</modules>
<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <!-- ... -->
</handlers>

If it still dosen't respond to OPTIONS request for some method in your controller i see that i have added AcceptVerbs Attribute like that:

// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
[AcceptVerbs("OPTIONS", "GET")]
public UserInfoViewModel GetUserInfo()
{
    ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);

    return new UserInfoViewModel
    {
        Email = User.Identity.GetUserName(),
        HasRegistered = externalLogin == null,
        LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
    };
}
Obelixx
  • 117
  • 5
  • is this code for .net or .net core? i didn't work for me. i've placed the class under the "models" folder, but when trying to edit web.config i had no _models_ tag, and when adding it under _handlers_ it asked for path and verb attributes, which eventually and anyway caused it to fail compilation – yoad w Dec 07 '18 at 18:52
  • __ was the line i added – yoad w Dec 07 '18 at 18:52
1

Solution
I think the problem was actually both front- and back-end related.
Microsoft's Web API 2's tutorial doesn't seem to mention its lack of support for the OPTIONS header that is generated by some clients as "pre-flight" request. In addition, the parameters I was using in Vue-Resource's also caused some problems.
Back-end:
1. I changed the default behavior that caused the server to drop all requests with OPTIONS header in Global.asax.cs, thanks Obelixx:

if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
   {
    //Response.Flush();
    Response.StatusCode = 200;
    Response.AddHeader("Access-Control-Allow-Headers", "content-type,accept,authorization");
    Response.AddHeader("Access-Control-Allow-Origin", "*");
    Response.AddHeader("Access-Control-Allow-Credentials", "true");
    Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE");
    Response.AddHeader("Content-Type", "application/json");
    Response.AddHeader("Accept", "application/json");
    Response.End();
   }

I'm really not sure that's the best solution, I'm sure there's a perfectly good reason why .net programmers thought all OPTIONS requests should be dropped, this needs deeper digging into it, so use with caution.

2. as can be seen here, I also added an OPTIONS actions in my controller class:

// OPTIONS: allow pre-flight
public HttpResponseMessage Options()
{
    return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}

front-end:
1. vue-resource's API says has this signature for POST request:

post(url, [body], [config])

but also says you can include the body inside the config paramter.
well, that didn't work for met. so instead of this:

this.$http.post('http://localhost:59837/api/Employees', 
{params: {id: '2'}, body: credentials, method: 'POST', headers: {'Content-Type': 'text/plain'}})

I did this:

this.$http.post('http://localhost:59837/api/Employees', credentials, 
{headers: {'Content-Type': 'application/json'}})

notice how i took the body: credentials out and just used credentials as a separate argument.

2. I also changed the 'Content-Type' header from text/plain to application/json (microsoft says that text/plain types should prevent the pre-flight, but that only caused my json formatter to fail parsing credentials).
----
So that's the solution for now, like I said I had both front- and back-end related fixes, I'm not sure if there were really so many bugs or just an undiscovered one that I patched in many places.
I hope this helps other people.

clayRay
  • 683
  • 1
  • 14
  • 32
yoad w
  • 315
  • 3
  • 10