简体   繁体   中英

Enable OPTIONS header for CORS on .NET Core Web API

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.

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();
}

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://docs.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());

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

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

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.

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

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

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();

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`

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();

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();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM