reactjsasp.net-coreasp.net-web-apiaxioscors

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.


Solution

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