65

I solved this problem after not finding the solution on Stackoverflow, so I am sharing my problem here and the solution in an answer.

After enabling a cross domain policy in my .NET Core Web Api application with AddCors, it still does not work from browsers. This is because browsers, including Chrome and Firefox, will first send an OPTIONS request and my application just responds with 204 No Content.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Niels Brinch
  • 3,033
  • 9
  • 48
  • 75
  • What is a specific scenario where this fails? If it's "it fails all the time for any chrome/ff browser doing CORS" then how is this not covered already by the framework? Seems like that would be a pretty huge omission. – ssmith Feb 14 '17 at 13:41
  • I agree. However, that is how it is. The framework will allow you to do CORS with built-in features, but it does not handle OPTIONS calls and this is a requirement for normal use of cross-domain api calls from browsers. However, you can avoid it by making a simpler call, like setting type to text/plain and a few other things. Then the browser won't do the OPTIONS call first. – Niels Brinch Feb 15 '17 at 11:56
  • IIS should be the one who handles the things, so anyone reading this after Nov 2017 should use IIS CORS module, https://blogs.iis.net/iisteam/introducing-iis-cors-1-0 – Lex Li Apr 08 '19 at 00:42
  • Yes or on Azure App Service, it seems for the last few years, it has been sufficient to set up CORS in Azure and allow certain domains or all domains (*). The above will work on Azure when NO domain is configured in CORS, which apparently means that Azure lets the application handle CORS itself. – Niels Brinch Jan 05 '21 at 13:04
  • https://stackoverflow.com/a/76279684/7186739 – Billu May 19 '23 at 08:05

13 Answers13

52

Add a middleware class to your project to handle the OPTIONS verb.

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

namespace Web.Middlewares
{
    public class OptionsMiddleware
    {
        private readonly RequestDelegate _next;

        public OptionsMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            return BeginInvoke(context);
        }

        private Task BeginInvoke(HttpContext context)
        {
            if (context.Request.Method == "OPTIONS")
            {
                context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)context.Request.Headers["Origin"] });
                context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
                context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, POST, PUT, DELETE, OPTIONS" });
                context.Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
                context.Response.StatusCode = 200;
                return context.Response.WriteAsync("OK");
            }

            return _next.Invoke(context);
        }
    }

    public static class OptionsMiddlewareExtensions
    {
        public static IApplicationBuilder UseOptions(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<OptionsMiddleware>();
        }
    }
}

Then add app.UseOptions(); this as the first line in Startup.cs in the Configure method.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseOptions();
}
matt_lethargic
  • 2,706
  • 1
  • 18
  • 33
Niels Brinch
  • 3,033
  • 9
  • 48
  • 75
  • 1
    I did this exact thing and can get the request to hit the Middleware, but it returns a "The requested URL can't be reached The service might be temporarily down or it may have moved permanently to a new web address. [chrome socket error]: A connection was reset (corresponding to a TCP RST)" error. What am I doing incorrectly? – crackedcornjimmy Jun 13 '17 at 19:31
  • 1
    I don't know. To troubleshoot I would open Fiddler and check the details of the request and response. – Niels Brinch Jun 19 '17 at 10:18
  • 7
    +1, but one thing need to be adjusted, do not call _next.Invoke if method is options, the request should end after calling `context.Response.WriteAsync("OK");`, so change `Invoke` implementation to be: `if (context.Request.Method != "OPTIONS") { await this._next.Invoke(context); }` – Amen Ayach Jul 07 '17 at 13:14
  • It's not safe. Why don't you configure CORS correctly instead? – Der_Meister Aug 23 '18 at 06:04
  • @Der_Meister Please educate me. My use case is that I want my API to be available from any website. – Niels Brinch Aug 23 '18 at 10:43
  • 1
    You may use CorsPolicyBuilder.AllowAnyOrigin(). Your answer doesn't have any disclaimer, somebody may find it and apply without understanding. – Der_Meister Aug 24 '18 at 07:05
  • 1
    This worked brilliantly for our Angular 6 frontend with a .NET Core backend. This together with the "AddCors" that allows the following: AllowAnyOrigin, AllowAnyMethod, AllowAnyHeader, And AllowCredentials works just fine. Our API is now completely public. We had to add the X-Auth header to the OPTIONS middleware, but that's because we use X-Auth from our client to our backend. – MortenMoulder Oct 18 '18 at 06:19
  • Just used this in my application but it's not working. Is there anything i'm not doing right? Here is a link to my question to https://stackoverflow.com/questions/53479946/how-to-handle-option-header-in-dot-net-core-web-api – Mcbaloo Nov 26 '18 at 11:54
  • Microsoft gave an official solution after several months of this answer, https://blogs.iis.net/iisteam/introducing-iis-cors-1-0 – Lex Li Apr 08 '19 at 00:41
  • 3
    @AmenAyach doesn't matter because inside the if statement it returns `context.Response.WriteAsync("OK");` so the `_next.invoke(context)` is never reached if the method is OPTIONS – Jesse de gans Dec 18 '19 at 14:13
34

I know it has been answered. Just answering with the updated information. So it would help others.

It is now built into the ASP.NET Core framework.

Just follow https://learn.microsoft.com/en-us/aspnet/core/security/cors

and replace

    app.UseCors(builder =>
   builder.WithOrigins("http://example.com"));

with

        app.UseCors(builder =>
       builder.WithOrigins("http://example.com")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials());
Bernard Vander Beken
  • 4,848
  • 5
  • 54
  • 76
Jeyara
  • 2,198
  • 1
  • 23
  • 26
  • 6
    Does it work? The issue was that Chrome sends an "OPTIONS" call first and does not get a go-ahead so the real call never comes from the Chrome browser. Is that what the "AllowAnyMethod" solves? Edit: I see now this is exactly what they addressed in the article you linked to! – Niels Brinch Mar 21 '18 at 10:32
  • 1
    fyi: If you don't want to allow **any** header or method, you can use the methods `WithHeaders`/`WithMethods` – Gerrit-K Jun 07 '18 at 07:44
  • 2
    by doing this I get `204 No Content` in response status code – Nitin Sawant Jan 29 '19 at 14:11
  • I also get a `204 No Content` response when trying this – Mark Entingh Apr 16 '19 at 04:57
  • 3
    Is a `204 No Content` a problem? Seems like the only info you should expect from an `OPTIONS` request is in the headers. No? [Mozilla seems to agree](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS). – Dan Narsavage Jul 25 '19 at 14:57
  • 2
    This needs to go into `Configure()` in `Startup.cs` – Luke Nov 22 '19 at 13:15
25

This worked for me:

Make sure that this:

app.UseCors(builder => {
    builder.AllowAnyOrigin();
    builder.AllowAnyMethod();
    builder.AllowAnyHeader();
});

Occurs before any of these:

app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseCookiePolicy();

Remember, we are dealing with a "pipeline". The cors stuff has to be first.

-gimzani

Alisson Reinaldo Silva
  • 10,009
  • 5
  • 65
  • 83
user1628627
  • 447
  • 1
  • 6
  • 8
  • The question is whether these settings are applied upon an OPTIONS call – Niels Brinch Apr 26 '19 at 09:06
  • 2
    Thank you, this worked to me. I would like to highlight that the cors stuff must be first before any other HTTP request pipeline configuration method, as @NielsBrinch said. – Acemond Feb 24 '20 at 11:17
  • 1
    I wonder why there are no warnings from the IDE or server about mistakes like these. These are simple pitfalls unsuspecting developers could find themselves in and spend extra hours on hours trying to debug. I guess stackoverflow has helped curb ALOT of developer depression. – Prince Owen Apr 07 '20 at 14:56
7

There is no need in an additional middleware. As already mentioned above the only thing needed is the OPTIONS method allowed in Cors configuration. You may AllowAnyMethod as suggested here: https://stackoverflow.com/a/55764660/11921910

But it's safer to just allow the specific stuff like this:

app.UseCors(builder => builder
.WithOrigins("https://localhost", "https://production.company.com") /* list of environments that will access this api */
.WithMethods("GET", "OPTIONS") /* assuming your endpoint only supports GET */
.WithHeaders("Origin", "Authorization") /* headers apart of safe-list ones that you use */
);

Some headers are always allowed: https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header

5

AspNetCoreModuleV2 OPTIONS problem

.Net core module does not know how to handle OPTIONS which causes a preflight CORS problem, so the solution is to exclude the OPTIONS verb from being handled by it. It's done by replacing the * with the verbs you want except the OPTIONS. Don't worry, the OPTIONS verb will be handled by the default loaded OPTIONSHandler:

IIS

Solution: Modify web.config

 <add name="aspNetCore" path="*" verb="* modules="AspNetCoreModuleV2" resourceType="Unspecified" />

Make it like this:

<add name="aspNetCore" path="*" verb="GET,POST,PUT,DELETE" modules="AspNetCoreModuleV2" resourceType="Unspecified" />

IIS Express: For Visual Studio Debugger

I tried modifying .vs\ProjectName\config\applicationhost.config at the bottom of the file but of no hope. Thus, in this specific case, you can use the chosen answer.

Shadi Alnamrouti
  • 11,796
  • 4
  • 56
  • 54
  • This really fixed my issue. Thanks a lot. This was the correct solution for me as I was using dotnet core 7 API hosted on IIS 8.5. – Dileep KK Apr 17 '23 at 06:38
1

I wanted to allow this for a single method, not using a middleware to allow this on any method. This is what I ended doing:

Manual handling of the 'OPTIONS' method

[HttpOptions("/find")]
public IActionResult FindOptions()
{
    Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)Request.Headers["Origin"] });
    Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
    Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST, OPTIONS" }); // new[] { "GET, POST, PUT, DELETE, OPTIONS" }
    Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
    return NoContent();
}

[HttpPost("/find")]
public async Task<IActionResult> FindOptions([FromForm]Find_POSTModel model)
{
    AllowCrossOrigin();
    
    // your code...
}

private void AllowCrossOrigin()
{
    Uri origin = null;
    Uri.TryCreate(Request.Headers["Origin"].FirstOrDefault(), UriKind.Absolute, out origin);

    if (origin != null && IsOriginAllowed(origin))
        Response.Headers.Add("Access-Control-Allow-Origin", $"{origin.Scheme}://{origin.Host}");
}

And of course, you can implement IsOriginAllowed as you wish

private bool IsOriginAllowed(Uri origin)
{
    const string myDomain = "mydomain.com";
    const string[] allowedDomains = new []{ "example.com", "sub.example.com" };

    return 
           allowedDomains.Contains(origin.Host) 
           || origin.Host.EndsWith($".{myDomain}");
}

You can find more details on how to enable CORS for POST requests on a single endpoint

Community
  • 1
  • 1
Jean
  • 4,911
  • 3
  • 29
  • 50
1

Actually, none of the answers worked for me but I finally figured out what is the problem and I can't believe it I just moved app.UserCors("PolicyName"); before app.UseAuthorization(); and it started working!

I thought this might be helpful to someone.

services.AddCors(options =>
{
  options.AddPolicy("EnableCORS", bl =>
  {
    bl.WithOrigins(origins)
      .AllowAnyMethod()
      .AllowAnyHeader()
      .AllowCredentials()
      .Build();
  });
});


..........................
app.UseAuthentication();
app.UseCors("EnableCORS");
.....
app.UseAuthorization();
Andro
  • 2,232
  • 1
  • 27
  • 40
ramin azadi
  • 59
  • 2
  • 5
1

If you are using IIS and Windows Authentication and Cors, then make sure you enable both Windows (GET/PUT/POST/DELETE/ETC) and Anonymous Authentication (used for OPTIONS/Preflight) within IIS. Ran across this because it worked fine with NPM RUN SERVE, but didn't work in IIS because I had anonymous disabled. I assumed Windows Authentication was sufficient, and it was until I tried to PUT/POST something did I realize there was a problem. Preflight is required for PUT/POST and apparently doesn't work with Windows Authentication. Target framework is .NET 6.0.

builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder
            .WithOrigins(
                "https://myserver",
                "http://localhost:8080")
            .AllowCredentials()
            .WithHeaders(HeaderNames.ContentType, "x-custom-header")
            .AllowAnyMethod() 
            .WithExposedHeaders(HeaderNames.ContentType, "x-custom-header")
            ;
        });
});

// Compressed responses over secure connections can be controlled with the EnableForHttps option, which is disabled by default because of the security risk.
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
});

// SQL
string connectionString = builder.Configuration.GetConnectionString("WebApiDatabase");
builder.Services.AddDbContext<otscorecard_2022_webapi.Models.WebApiDbContext>(options =>
    options.UseSqlServer(connectionString)
);

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
   .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy.
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseCors();  // IIS: enable both Windows (GET/PUT/POST/DELETE/ETC) and Anonymous Authentication (used for OPTIONS/PreFlight)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseResponseCompression();
app.Run();
1

You might be blocking the OPTIONS http verb in IIS. Check the "HTTP Verbs" Tab in Request Filtering settings in your IIS. Remove the highlighted option as shown in the image from the link below.

IIS Request Filtering

0

I want to put a specific answer for my specific situation where i was testing both the api and the client web app locally. I know this is a late entry but CORS has changed so much in dot net core, i thought, newcomers like me might benefit with a full post.

For me, it was two issues that occurred back to back.

  1. CORS rejection error
  2. and also OPTIONS issue on firefox (I assume chrome would do the same)
  3. also my API is running HTTPS
  4. web app is without HTTPS
  5. both of them running locally, mentioning this again, for clarity.

First, this goes to public void ConfigureServices(IServiceCollection services)

        //lets add some CORS stuff 
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder => {
                builder.WithOrigins("http://localhost:3000",
                                    "http://www.contoso.com");
                builder.AllowAnyMethod();
                builder.AllowAnyHeader();
                builder.AllowCredentials();
            });
        });

and then, this, goes to, public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

  app.UseCors();
Jay
  • 2,648
  • 4
  • 29
  • 58
0

welcome .

[HttpOptions("/find")] public IActionResult FindOptions()

{
    Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)Request.Headers["Origin"] });
    Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
    Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST, OPTIONS" }); // new[] { "GET, POST, PUT, DELETE, OPTIONS" }
    Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
    return NoContent();
}`enter code here`
  • 1
    This question already contains multiple answers and an accepted answer. Can you explain where your answer differs from the other answers? Also know that Code-only answers are not useful in the long run. – 7uc1f3r Jul 30 '20 at 11:42
0

Please ensure that you also change the COrs settings in appsettings.json if you have this kind of configuration.

  "App": {
    "ServerRootAddress": "http://localhost:44301/",
    "ClientRootAddress": "http://localhost:4200/",
    "CorsOrigins": "*",
    //"CorsOrigins": "http://*.mycompany.com,http://localhost:4200,http://localhost:9876",
    "SwaggerEndPoint": "/swagger/v1/swagger.json",
    "AllowAnonymousSignalRConnection": "true",
    "HomePageUrl": "/index.html"
  },

Because I failed checking this, I wasted 1 day trying to find why CORS is not working.

exejee
  • 1
  • 1
0
builder.Services.AddCors(options =>
{
    options.AddPolicy(name: _policyName,
    policy =>
    {
        policy.WithOrigins("https://Google.com") //for specific
            //.AllowAnyOrigin(); //Allow all origin
            .WithMethods("PUT", "DELETE", "GET"); //for specific
            //.AllowAnyMethod(); //Allow all methods
            .WithHeaders(HeaderNames.ContentType, "ApiKey"); //for specific header
            //.AllowAnyHeader(); //Allow all headers

    });
});

References: https://geeksarray.com/blog/how-to-setup-cors-policies-in-aspnet-core-web-api and https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0

Billu
  • 2,733
  • 26
  • 47