Home > Net >  OPTIONS request returns 500 AmbiguousMatchException on .Net Core API
OPTIONS request returns 500 AmbiguousMatchException on .Net Core API

Time:10-05

I am working on a .net core project building a REST API. I've basically implemented it and tested it with a REST client and all is working well. However when testing on a real scenario with a browser, the browser first sends an OPTIONS request which then fails, so ultimately my request fails also. I have tested the OPTIONS request through the REST client and get the same behaviour so its not specific to the browser. Is there anything special I need to do to make this work - expected this would be handled automatically.

I start the web host as follows:

CreateWebHostBuilder(args).Build().Run();

public static IHostBuilder CreateWebHostBuilder(string[] args)
        {
            string bindAddress = "127.0.0.1";
            int portAddress = 5000;

            string engineUrl = string.Format("https://{0}:{1}", bindAddress, portAddress);

            var host = Host.CreateDefaultBuilder(args)
                /*.ConfigureLogging(logging =>
                {
                    logging.ClearProviders();
                })*/
                
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<EngineAPI.Startup>();
                    webBuilder.UseUrls(engineUrl);
                    webBuilder.UseKestrel(options =>
                    {
                        options.Listen(IPAddress.Loopback, 5000); //HTTP port
                    });
                });
            return host;
        } 

And my Startup.cs is as follows

public class Startup
    {
        public static BitsLibrary bitsLibrary { get; private set; }
        readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

        //public static Dictionary<string, string> databaseSettings { get; private set; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;

            loadCrashCatchConfiguration();
        }

        public void loadCrashCatchConfiguration()
        {
            INIParser iniparser = new INIParser("crashcatch.ini");
            StaticSettings.General.Host = iniparser.readStringValue("general", "host");
            StaticSettings.Datadog.AppName = iniparser.readStringValue("datadog", "app_name");
            StaticSettings.Datadog.Enabled = iniparser.readBooleanValue("datadog", "enabled", false);
            //StaticSettings.Redis.Host = iniparser.readStringValue("redis", "host", "127.0.0.1");

            if (string.IsNullOrEmpty(StaticSettings.General.Host))
            {
                throw new System.Exception("Host not set in crashcatch.ini file");
            }
            
        }

        public static void setBitsLibrary(BitsLibrary bitsLibrary)
        {
            Startup.bitsLibrary = bitsLibrary;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(name: MyAllowSpecificOrigins,
                                  builder =>
                                  {
                                      builder.WithOrigins("*")
                                      .AllowAnyHeader()
                                      .AllowAnyMethod();
                                  });
            });

            services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
            services.AddControllers();

            

            // services.AddResponseCaching();
            services.AddControllers();
        }

        

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseAuthentication();
            app.UseCors(MyAllowSpecificOrigins);
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
            app.Use(async (context, next) =>
            {
                context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
                
                await next();
            });
        }
    }

CodePudding user response:

At a first glance, the middleware order is incorrect.

The order (of middleware components) is critical for security, performance, and functionality.

There are 2 issues:

  1. You're calling app.UseAuthorization(); before app.UseCors(MyAllowSpecificOrigins); - it should be called after
  2. You're calling app.UseAuthorization(); before app.UseAuthentication(); - it should be called after

The ordering must be as follows:

  1. app.UseRouting();
  2. app.UseCors();
  3. app.UseAuthentication();
  4. app.UseAuthorization();
  5. app.UseResponseCaching(); (which you don't have but useful to know)

Replace this section:

...
app.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseCors(MyAllowSpecificOrigins);
...

With:

...
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthentication();
app.UseAuthorization();
...

I suspect the above to weirdly be the cause for your AmbiguousMatchException, which typically means your request's route has matched multiple actions.

You haven't shared your controllers but once the above issue is fixed, if you're still having issues, double check your actions to make sure each one maps to only 1 specific route.


As a side note, you can also use the CorsPolicyBuilder.AllowAnyOrigin method instead of using builder.WithOrigins("*").

options.AddPolicy(name: MyAllowSpecificOrigins,
                  builder =>
                  {
                      builder
                      .AllowAnyOrigin()
                      .AllowAnyHeader()
                      .AllowAnyMethod();
                  });

P.S. Allowing any origin is fine for local development but remember to not allow every origin once deployed (unless you're creating an API for public consumption as you've clarified) :)

  • Related