简体   繁体   中英

How to resolve CORS error in REACT with ASP.NET Core

I have an ASP.NET Core Web API and a separate React app. The Web API uses Windows Authentication. When deployed to the server, I don't have any issues, but when I try to run the app locally, I get CORS errors and only on POST actions. Here's what I have in my Startup.cs :

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using Server.DAL;
using Server.Data;

namespace Server
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<AppDbContext>(options => options
               .UseSqlServer(Configuration.GetConnectionString("DatabaseConnection"))
               .UseLazyLoadingProxies());

            services.AddScoped<IUnitOfWork, UnitOfWork>();

            services.AddControllers();

            services.AddCors(options => options.AddPolicy("CorsPolicy",
                builder =>
                {
                    builder
                    .WithOrigins("http://localhost:3000")                        
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials();
                }));    
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseSwagger();

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors("CorsPolicy");

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

And, here's my axios configuration in the React app:

import axios, { AxiosInstance } from "axios";

let axiosInstance: AxiosInstance;

const axiosBaseConfig = {
  baseURL: process.env.REACT_APP_BASE_URL,
  timeout: 1000 * 20,
  withCredentials: true,
  headers: {
    Accept: "applicaiton/json",
    "Content-Type": "application/json",
  },
};

export const getAxios = () => {
  if (axiosInstance) {
    return axiosInstance;
  }

  axiosInstance = axios.create(axiosBaseConfig);

  return axiosInstance;
};

Is there anything I'm missing or doing wrong here?

UPDATE:

Here's the CORS error I'm gettings:

Access to XMLHttpRequest at 'https://localhost:44376/api/reservation' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Try this:-

In your ConfigureServices

services.AddCors();

And your configure method app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:3000"));

Hope it will resolve your issue.

Access to XMLHttpRequest at 'https://localhost:44376/api/reservation' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Please note that a CORS preflight request (using the HTTP OPTIONS method) is used to check whether the CORS protocol is understood and a server is aware using specific methods and headers.

And the HTTP OPTIONS requests are always anonymous, if you enabled Windows Authentication and disabled anonymous access on local project, which would cause web server not correctly respond to the preflight request.

For local testing purpose, you can do windows authentication test, and then separately do CORS test by allowing anonymous access.

The Web API uses Windows Authentication. When deployed to the server, I don't have any issues, but when I try to run the app locally, I get CORS errors

If the IIS CORS module has been installed and CORS policy is configured for the app/site on your server, or both Windows Authentication and Anonymous Authentication are enabled for your site, which might be the cause of the app can work well on server but not on local IIS express.

I would make two changes just for development and running on the local machine:

  1. move the cors call before adding the controllers

  2. use any origin like this

     public void ConfigureServices(IServiceCollection services) { services.AddDbContext<AppDbContext>(options => options .UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")) .UseLazyLoadingProxies()); services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddCors(options => options.AddPolicy("CorsPolicy", builder => { builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); })); services.AddControllers(); }

Also if you are using IIS you might want to take a look at this post since it could be a preflight issue:

Asp.net core web api using windows authentication - Cors request unauthorised

You might need to add this to your launch settings allowing both window and anonymous for preflights:

{
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:3000",
      "sslPort": 0
    }
  },
 {... more settings if any}
}

You can try to add like the below:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
 ...
 app.UseCors(cors => cors.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
 app.UseAuthorization();
 ...
}

public void ConfigureServices(IServiceCollection services) {
 ...
 services.AddCors(c => { c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin()); });
 services.AddControllers();
 ...
}

Try adding [EnableCors("NAME_OF_YOUR_POLICY")] on every endpoints that require CORS.

Example:

[EnableCors("CorsPolicy")]
[Route("api/v1/test)]
public class TestApiController 
{
  ...
}

Source:

Your code looks good at first sight, similar to some code of mine, but I don't like this setup, because these are unrelated domains and this is asking for browser problems :

  • Web URL: http://localhost:3000
  • API URL: https://localhost:44376

VERIFY API HEADERS

First verify that the API is behaving correctly with a simple curl request:

curl -i -X OPTIONS "https://localhost:44376/api/reservation" \
-H "origin: http://localhost:3000"

For browser testing I tend to use an incognito window to prevent issues where old CORS headers have been cached.

MOVE TO A SAME SITE SETUP

I would think that CORS will work, but I know you will get dropped cookies later on POST requests, since the API cookie will be considered third party. Try changing to this setup instead, where both browser and API are in the same site of mycompany.com. Note that you should either use http or https for both, though different ports is fine:

The domains can easily be registered in your hosts file by adding entries like this, which points them to localhost:

127.0.0.1 localhost www.mycompany.com api.mycompany.com
:1        localhost

You will then get a better developer experience in the browser with cookies. And you can see if it makes a difference to CORS behaviour.

curl -i -X OPTIONS "http://api.mycompany.com:44376/api/reservation" \
-H "origin: http://www.mycompany.com:3000"

SUMMARY

I am not entirely sure if this will resolve your CORS problem, but hopefully it gives you some pointers that will help later. Out of interest this is also the type of setup we recommend at Curity, so that cookies work deterministically - as for this CORS related solution of ours:

if you have a create-react-app project the way for solving this is by adding a property proxy to your package.json file with the url of the backend

{
...
   "proxy": "https://localhost:44376"
...
}

now yow fetches url must be relative

fetch("/api/users")

instead of

fetch("https://localhost:44376/api/users")

Fei Han and Jonathan Alfaro put me in the right direction and I finally found the solution. As Fei Han rightly points out in his answer, the CORS preflight requests are always anonymous and they use an HTTP OPTIONS method. So, here's what I did to resolve the issue.

  1. Enable anonymous authentication in launchSettings.json :
  "iisSettings": {
    "windowsAuthentication": true,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:62682",
      "sslPort": 44376
    }
  },
  1. Create an Authenitcation Middleware like below, that returns 401 if the http request has any method other than OTHERS and the user is not authenticated.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace Server
{
    public class AuthenticationMiddleware
    {
        private readonly RequestDelegate _next;

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

        public async Task Invoke(HttpContext context)
        {
            BeginInvoke(context);
            await _next.Invoke(context);
        }

        private async void BeginInvoke(HttpContext context)
        {
            if (context.Request.Method != "OPTIONS" && !context.User.Identity.IsAuthenticated)
            {
                context.Response.StatusCode = 401;
                await context.Response.WriteAsync("Not Authenticated");
            }
        }
    }
}
  1. Use the middleware in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
 
    app.UseSwagger();

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseMiddleware<AuthenticationMiddleware>();

    app.UseCors("CorsPolicy");

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

It's very easy, this thing worked for me. Just install allow CORS browser extension for your respective browser. And turn it on while making API requests. You will not get any errors, also you don't need to change anything in your code!

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