.NET Core UseCors() does not add headers
Asked Answered
T

7

42

This would be a duplicate of How does Access-Control-Allow-Origin header work?, but the method there also isn't working for me. I'm hoping I'm just missing something.

I am trying to get a Access-Control-Allow-Origin header in my response from my .NET Core Web API, which I am accessing via AJAX.

I have tried several things. All, unless noted otherwise, have been in the Startup.cs file.

Method 1

As per the Microsoft Documentation:

public void ConfigureServices(IServiceCollection services)
{
    // Add database
    services.AddDbContext<DbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DbConnection")));

    // Add the ability to use the API with JSON
    services.AddCors();

    // Add framework services.
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            serviceScope.ServiceProvider.GetService<DbContext>().Database.Migrate();
            serviceScope.ServiceProvider.GetService<DbContext>().EnsureSeedData();
        }
    }

    app.UseCors(builder => builder.WithOrigins("https://localhost:44306").AllowAnyMethod());

    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
        Audience = Configuration["Authentication:AzureAd:Audience"],
    });

    app.UseMvc();
}

Method 2

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddCors(options => options.AddPolicy("AllowWebApp",
        builder => builder.AllowAnyMethod()
                          .AllowAnyMethod()
                          .AllowAnyOrigin()));
                          //.WithOrigins("https://localhost:44306")));

    // ...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // ...

    app.UseCors("AllowWebApp");

    // ...
}

I've also tried adding [EnableCors("AllowWebApp")] on both the Controller and Method.

From Postman, I get:

content-encoding → gzip
content-type → text/plain; charset=utf-8
date → Wed, 25 Jan 2017 04:51:48 GMT
server →Kestrel
status → 200
vary → Accept-Encoding
x-powered-by → ASP.NET
x-sourcefiles → =?UTF-8?B?[REDACTED]

I've also tried it in Chrome, and gotten similar headers.

If it matters, the method I'm trying to access has an Authorize attribute on it. But that part should be working fine (I'm at least getting a good response)

So, am I missing something very obvious, or did this get broken? I'm currently running version 1.1.0.


Edit adding JS and Controller Stub

function getContactPreviews(resultsCallback) {
    var xmlhttp = new XMLHttpRequest();

    xmlhttp.onreadystatechange = () => {
        if (xmlhttp.readyState == XMLHttpRequest.DONE && xmlhttp.status == 200) {
            resultsCallback(JSON.parse(xmlhttp.response));
        }
    }

    xmlhttp.open("GET", "https://localhost:44357/api/User/ContactsPreview", true);
    xmlhttp.setRequestHeader("Authorization", "Bearer " + localStorage.getItem("AuthorizationToken"));
    xmlhttp.send();
}

Controller Stub

[Authorize]
[Route("api/[controller]")]
public class UserController : ApiController
{
    [HttpGet(nameof(ContactsPreview))]
    [EnableCors("AllowWebApp")]
    public IEnumerable<Customer> ContactsPreview()
    {
        // ...
    }
}
Tryout answered 25/1, 2017 at 5:21 Comment(10)
How are you hitting the server with Postman? The header will only be returned for an OPTIONS requestAbc
@Rob. That's what I was missing. Less so on the Postman side (I had tried it in Chrome as well, and that was using the correct method), and more so on the controller side. I had [HttpGet] instead of [HttpOptions]. I had a feeling it'd be something dumb like that.Tryout
@Rob, however, Chrome is still failing. It's getting a 204 error. Postman works perfectly. Both are using the same Bearer token. Cache is disabled in Chrome.Tryout
That.. doesn't seem correct to me. You shouldn't have to mark your controller method as HttpOptions. Your code in Method 1 looks correct (just checked against my local project, which works). I feel like chrome was failing because it cached the pre-flight OPTIONS request, and postman was failing because you weren't sending an OPTIONS request.Abc
Even though the cache is disabled, can you inspect your network log and check that the OPTIONS preflight was actually sent?Abc
Also note that 204 is not an error. 204 is a success code, it means the server had no data.Abc
@Abc Yes, I know on the 204 part. Although there should be data. The API returns some JSON. Anyways, it's using the options method. Here's what Chrome is showing: imgur.com/a/M2FJxTryout
Okay, so 204 is expected in this case as this is the preflight request before sending the actual request. What methods does your controller action accept? Have you explicitly marked it as HttpPost if it's a post method for example?Abc
Let us continue this discussion in chat.Tryout
@Rob, thanks for that, Chrome was caching the OPTIONS preflight request in my case, and disabling the cache solved itCeilometer
T
40

The problem is that when using Bearer authentication (or any I would imagine), it adds a header "Authorization", and the server will only give an okay if the setup allows for that header.

There's two ways to solve the problem, and below is the only code needed. It goes in the Configure() method in Startup.cs in the Web API solution.

Method 1: Allow all headers

app.UseCors(builder => builder.WithOrigins("https://localhost:44306")
                                .AllowAnyMethod()
                                .AllowAnyHeader());

Method 2: Allow specific headers

app.UseCors(builder => builder.WithOrigins("https://localhost:44306")
                              .AllowAnyMethod()
                              .WithHeaders("authorization", "accept", "content-type", "origin"));

The extra headers are because, per the documentation:

Browsers are not entirely consistent in how they set Access-Control-Request-Headers. If you set headers to anything other than "*", you should include at least "accept", "content-type", and "origin", plus any custom headers that you want to support.

Tryout answered 26/1, 2017 at 6:20 Comment(0)
C
26

The Access-Control-Allow-Origin header is returned only if:

  1. The request includes an "Origin" header.
  2. The requested origin matches the CORS policy.

Then the server returns the ACAO-header with the origin URL as value.

The Origin header is usually set by the XMLHttpRequest object.

For more information, see How CORS works

Crispa answered 23/3, 2018 at 7:20 Comment(4)
This information helped me. I did set up everything correctly, but forgot to send the Origin header when testing with cURL.Cathepsin
Thank you so much for this. I spent hours to get this to work, turns out my configuration was correct but test apps weren't sending an originFarly
Thank you, had the same problem as nightblade9!Leandraleandre
And when matching the Origin header, that includes the https:// prefix... Duh!Ohaus
A
4

In Startup.cs file, add following

public CorsPolicy GenerateCorsPolicy(){
                var corsBuilder = new CorsPolicyBuilder();
                corsBuilder.AllowAnyHeader();
                corsBuilder.AllowAnyMethod();
                corsBuilder.AllowAnyOrigin(); // For anyone access.
                //corsBuilder.WithOrigins("http://localhost:56573"); // for a specific url. Don't add a forward slash on the end!
                corsBuilder.AllowCredentials();
                return corsBuilder.Build();
    }

In ConfigureServices method:

 services.AddCors(options =>
                {
                    options.AddPolicy("AllowAllOrigins", GenerateCorsPolicy());
                });

// To Apply CORS globally throughout the application // In Configure method, add

app.UseCors("AllowAllOrigins");  

[DisableCors]
Using DisableCors attribute, we can disable CORS for a controller or an action.

//To Enable CORS controller basis - If you apply globally you don't need this one.

[EnableCors("AllowAllOrigins")]  
public class HomeController: Controller {}  
Alfieri answered 5/6, 2018 at 19:44 Comment(1)
Is it worth adding here that adding all origins would be unsafe (effectively disabling Cors) and it would be better just to specify the origins you want to allow to avoid cross-site scripting being possible?Lighter
O
4

As of date 03/17/2019, .NET Core version 2.1:

This will possibly save some time to other poor souls...at some point I started to be frustrated and almost gave up on .NET Core WebApi as a separate project.

In real life circumstances, there are other configurations in Startup functions e.g. I had Swagger, DI registrations etc. I wasn't able to make bloody thing work until I put both AddCors() and UseCors() methods to be the first one getting called in configuration functions.

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("SomePolicy",
                    builder => builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader()
                        .AllowCredentials());
            });



 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseCors("SomePolicy");

After that, calls from Angular 6 app (Swagger Typescript client calls) started to work as a charm.

Ola answered 17/3, 2019 at 23:2 Comment(3)
@Tryout If in your Setup.cs you only have CORS setup, then there is no difference. However, it is more likely that you will have middleware(s) for errors, Facebook, Swagger... In that case, CORS enabling function have to be the first one in pipeline/called in configure functions. Otherwise, it might not work. Note that UseMvc() have to go after it also, but it is mentioned in lot of articles I saw. My point was; one have to be careful where and in what context is using these lines of code.Ola
I've been wrestling with this for hours now. I've seen this method detailed everywhere, but I never get the CORS headers no matter what I do. I just want those headers attached to EVERY response, but judging by what people are saying, it's a monumentally complex task!Triphthong
This was it for me. Files were uploading correctly, but the browser was still complaining. Swapping lines in correct order in Configure fixed it. app.UseCors("AllowAllOrigins") |> ignore; app.UseGiraffe routesDallas
F
4

I wasted hours on this problem today, only to discover it's because .Net Core 3 doesn't support preflight OPTIONS requests if you use the Endpoint routing RequirePolicy method of enabling CORS!

The official documentation does mention this, but it wasn't called out in an obvious warning block so I'd totally missed it.

The following will fix the problem, but it'll apply a global CORS policy, so caveat emptor!

Service Configuration:

public void ConfigureServices(IServiceCollection services)
{
    string[] corsOrigins = Configuration.GetSection("AllowedHosts").Get<string[]>();

    services.AddCors(options =>
    {
        options.AddPolicy("AllowConfiguredOrigins", builder => builder
            .WithOrigins(corsOrigins)
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()
        );
    });
...

Basically, don't do this:

public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseCors();    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers().RequireCors("AllowConfiguredOrigins");
    });
...

...do this instead

Configure()

public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseCors("AllowConfiguredOrigins");
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
...
Fiesta answered 5/5, 2021 at 14:57 Comment(1)
I'm not doing exactly what you said not to do, but is what I'm doing as bad? app.UseEndpoints(endpoints => { endpoints.MapDefaultControllerRoute().RequireAuthorization(); });Hasdrubal
D
4

In Startup.cs at the end of ConfigureServices() add this:

services.AddCors();

Then in Configure() at the top add this:

app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("*"));

// alternatively you could use .With... methods to specify your restrictions:
// app.UseCors(x => x.WithOrigins("http://domain1.com").WithMethods("GET","POST").WithHeaders("Authorization").WithExposedHeaders("*"));
Divisible answered 5/8, 2021 at 23:9 Comment(0)
A
2

I want to add one more possibility for those who may have followed the advice above and it is still not working. In my case I was not getting the header returned (or only getting it on the first request) because of the order of registration in the pipeline.

I changed the order from this:

app.UseResponseCaching();
app.UseHttpsRedirection();
app.UseCors("WideOpen");
app.UseMvc();

To this:

app.UseCors("WideOpen");
app.UseResponseCaching();
app.UseHttpsRedirection();
app.UseMvc();

That resolved my issue.

Alamo answered 1/9, 2020 at 17:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.