8

Trying to set up CORS with authentication. I have a Web API site up at http://localhost:61000 and a consuming web application up at http://localhost:62000. In the Web API Startup.cs, I have:

 public void ConfigureServices(IServiceCollection services)
 {
        services.AddCors(o => o.AddPolicy("MyPolicy", corsBuilder =>
        {
            corsBuilder.WithOrigins("http://localhost:62000")
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials();
        }));
        IMvcBuilder builder = services.AddMvc();
        // ...
}

// ...

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
        app.UseCors("MyPolicy");
        app.UseDeveloperExceptionPage();
        app.UseDefaultFiles();
        app.UseStaticFiles();
        app.UseMvc();
}

All the doucmentation seems to indicate that should be all I need. In my app's Javascript, I call:

    $.ajax({
        type: 'POST',
        url: "http://localhost:61000/config/api/v1/MyStuff",
        data: matchForm.serialize(),
        crossDomain: true,
        xhrFields: { withCredentials: true },
        success: function (data) {
            alert(data);
        }
    });

And I get in Chrome: Failed to load http://localhost:61000/config/api/v1/MyStuff: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:62000' is therefore not allowed access.

...and in Firefox: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:61000/config/api/v1/MyStuff. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

What am I missing? This should be all I need to enable CORS, I thought, but clearly there is something else missing.

Lukas
  • 1,699
  • 1
  • 16
  • 49
Patrick Szalapski
  • 8,738
  • 11
  • 67
  • 129
  • Does your request completes successfully? Please check your logs/exceptions. All HTTP response headers are cleared in the case of an error, including CORS headers. – Henk Mollema Apr 18 '18 at 13:27
  • Looks correct to me. Have you tried testing the API with an application like Postman to see if the header response is being returned as it should be? Or maybe backing down the options to see which one is causing the issue. – Adam Vincent Apr 18 '18 at 13:28
  • The Access-Control-Allow-Origin header is not being returned. I have no options to "back down", as the only thing I can get working is no CORS at all. – Patrick Szalapski Apr 18 '18 at 13:30
  • Sorry, by backing down the options I meant be less strict, as in temporarily remove `WithOrigins("http://localhost:62000")` to allow any origin. Or removing `AllowCredentials()` from both the API, and the AJAX call. – Adam Vincent Apr 18 '18 at 13:44
  • I have `if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); }` in my Startup.cs. The `DeveloperExceptionPageMiddleware` calls `context.Response.Clear();` before displaying the message. – JJS May 16 '18 at 22:40

4 Answers4

10

For ASP.NET Core 2.1 and earlier:

It seems there was an error in my code, but I got the obscure error noted instead of getting an ASP.NET-generated error page. It turns out that the CORS headers are indeed properly applied at first, but then they are stripped off any ASP.NET middleware-generated errors. See also https://github.com/aspnet/Home/issues/2378 .

I used that link to figure out this class

using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;

namespace MySite.Web.Middleware
{
    /// <summary>
    /// Reinstates CORS headers whenever an error occurs.
    /// </summary>
    /// <remarks>ASP.NET strips off CORS on errors; this overcomes this issue,
    ///  explained and worked around at https://github.com/aspnet/Home/issues/2378 </remarks>
    public class MaintainCorsHeadersMiddleware
    {
        public MaintainCorsHeadersMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        private readonly RequestDelegate _next;

        public async Task Invoke(HttpContext httpContext)
        {
            // Find and hold onto any CORS related headers ...
            var corsHeaders = new HeaderDictionary();
            foreach (var pair in httpContext.Response.Headers)
            {
                if (!pair.Key.ToLower().StartsWith("access-control-")) { continue; } // Not CORS related
                corsHeaders[pair.Key] = pair.Value;
            }

            // Bind to the OnStarting event so that we can make sure these CORS headers are still included going to the client
            httpContext.Response.OnStarting(o => {
                var ctx = (HttpContext)o;
                var headers = ctx.Response.Headers;
                // Ensure all CORS headers remain or else add them back in ...
                foreach (var pair in corsHeaders)
                {
                    if (headers.ContainsKey(pair.Key)) { continue; } // Still there!
                    headers.Add(pair.Key, pair.Value);
                }
                return Task.CompletedTask;
            }, httpContext);

            // Call the pipeline ...
            await _next(httpContext);
        }
    }
}

And then I added it to my site configuration in Startup.cs:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseCors(...);
        app.UseMiddleware<MaintainCorsHeadersMiddleware>();

        ...
        app.UseMvc();
    }
Patrick Szalapski
  • 8,738
  • 11
  • 67
  • 129
2

ASP.NET Core 2.2.0 Answer

This issue is now fixed. CORS headers are now returned even when exceptions are thrown and a 500 response is returned.

ASP.NET Core <= 2.1.0 Answer

CORS Headers were stripped from the response when an exception is thrown and a 500 response is returned.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
1

I was having this same issue on a Web API project using OWIN middleware, where a wrong package version was causing errors on the API side (hidden on the client side because CORS headers were stripped on the response, which obscured the original error). I implemented a similar solution to yours, sharing here because I could not find any similar example using OWIN on the web:

using System;
using System.Linq;
using System.Threading.Tasks;
using Owin;
using Microsoft.Owin;
using Microsoft.Owin.Cors;

namespace App_Server
{
    using AppFunc = Func<IDictionary<string, object>, Task>;
    partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            app.Use(new Func<AppFunc, AppFunc>(RetainHeaders));
            ....
            (other startup tasks)
        }

        private AppFunc RetainHeaders(AppFunc next)
        {
            AppFunc appFunc = async (IDictionary<string, object> context) =>
            {
                IOwinContext httpContext = new OwinContext(context);
                var corsHeaders = new HeaderDictionary(new Dictionary<string, string[]>());

                foreach (var pair in httpContext.Response.Headers)
                {
                    if (!pair.Key.ToLower().StartsWith("access-control-")) { continue; } //not a CORS header
                    corsHeaders[pair.Key] = pair.Value.First();
                }

                httpContext.Response.OnSendingHeaders(o =>
                {
                    var localcontext = new OwinContext((IDictionary<string, object>)o);
                    var headers = localcontext.Response.Headers;
                    //make sure headers are present, and if not, add them back
                    foreach (var pair in corsHeaders)
                    {
                        if (headers.ContainsKey(pair.Key)) { continue; }
                        headers.Add(pair.Key, pair.Value);
                    }
                }, context);

                await next.Invoke(context);
            };
            return appFunc;
        }
}

This was a quite a process to work out given how poorly documented the OWIN packages are for .Net, so I hope it helps someone else who comes across it looking for a solution.

Ryan I.
  • 21
  • 4
0

In my case the CORS headers were lost because I was making an "application/json" content type request, and in CORS this type of request send first an OPTIONS method, after that, the regular POST is requested. But the OPTIONS was been managed by a Middleware code in my .Net Core pipeline with something like this:

        if (context.Request.Method == "OPTIONS")
        {
            context.Response.StatusCode = (int)HttpStatusCode.OK;
            await context.Response.WriteAsync(string.Empty);
        } 

Once I remove the middleware those requests were flawless attended.

Fidel Orozco
  • 998
  • 11
  • 19