Cómo habilitar CORS en ASP.net Core WebAPI

Lo que trato de hacer

Tengo una API web ASP.Net Core alojada en un Azure Free Plan ( http://andrewgodfroyportfolioapi.azurewebsites.net/swagger/ ) (Código fuente: https://github.com/killerrin/Portfolio-Backend ).

También tengo un sitio web de cliente que quiero que consum esa API. La aplicación de cliente no se alojará en Azure, sino que se alojará en páginas de Github o en otro servicio de alojamiento web al que tenga acceso. Debido a esto, los nombres de dominio no se alinearán.

Al analizar esto, necesito habilitar CORS en el lado de API web, sin embargo, he intentado casi todo durante varias horas y se niega a trabajar.

Cómo tengo la configuración del cliente Es solo un cliente simple escrito en React.js. Estoy llamando a las API a través de AJAX en Jquery. El sitio React funciona, así que sé que no es eso. La llamada a la API de Jquery funciona como confirmé en el bash 1. Aquí es cómo hago las llamadas

var apiUrl = "http://andrewgodfroyportfolioapi.azurewebsites.net/api/Authentication"; //alert(username + "|" + password + "|" + apiUrl); $.ajax({ url: apiUrl, type: "POST", data: { username: username, password: password }, contentType: "application/json; charset=utf-8", dataType: "json", success: function (response) { var authenticatedUser = JSON.parse(response); //alert("Data Loaded: " + authenticatedUser); if (onComplete != null) { onComplete(authenticatedUser); } }, error: function (xhr, status, error) { //alert(xhr.responseText); if (onComplete != null) { onComplete(xhr.responseText); } } }); 

Lo que he intentado


Intento 1 – La forma “correcta”

https://docs.microsoft.com/en-us/aspnet/core/security/cors

He seguido este tutorial en el sitio web de Microsoft a una T, probando las 3 opciones de habilitarlo globalmente en Startup.cs, configurándolo en cada controlador y probándolo en cada acción.

Siguiendo este método, Cross Domain funciona, pero solo en una sola Acción en un solo controlador (POST en AccountController). Para todo lo demás, el middleware Microsoft.AspNetCore.Cors niega a establecer los encabezados.

Instalé Microsoft.AspNetCore.Cors través de NUGET y la versión es 1.1.2

Así es como lo tengo configurado en Startup.cs

  // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add Cors services.AddCors(o => o.AddPolicy("MyPolicy", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); })); // Add framework services. services.AddMvc(); services.Configure(options => { options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy")); }); ... ... ... } // This method gets called by the runtime. Use this method to configure //the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); // Enable Cors app.UseCors("MyPolicy"); //app.UseMvcWithDefaultRoute(); app.UseMvc(); ... ... ... } 

Como puede ver, estoy haciendo todo lo que le digo. Agregué Cors antes de MVC en ambas ocasiones, y cuando eso no funcionó, intenté poner [EnableCors("MyPolicy")] en cada controlador como tal

 [Route("api/[controller]")] [EnableCors("MyPolicy")] public class AdminController : Controller 

Attempt 2 – Brute Forcing it

https://andrewlock.net/adding-default-security-headers-in-asp-net-core/

Después de varias horas de probar el bash anterior, pensé que trataría de aplicar una fuerza bruta tratando de configurar manualmente los encabezados, forzándolos a ejecutar cada respuesta. Hice esto siguiendo este tutorial sobre cómo agregar manualmente encabezados a cada respuesta.

Estos son los encabezados que agregué

 .AddCustomHeader("Access-Control-Allow-Origin", "*") .AddCustomHeader("Access-Control-Allow-Methods", "*") .AddCustomHeader("Access-Control-Allow-Headers", "*") .AddCustomHeader("Access-Control-Max-Age", "86400") 

Estos son otros encabezados que probé que fallaron

 .AddCustomHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE") .AddCustomHeader("Access-Control-Allow-Headers", "content-type, accept, X-PINGOTHER") .AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Host, User-Agent, Accept, Accept: application/json, application/json, Accept-Language, Accept-Encoding, Access-Control-Request-Method, Access-Control-Request-Headers, Origin, Connection, Content-Type, Content-Type: application/json, Authorization, Connection, Origin, Referer") 

Con este método, los encabezados de Cross Site se están aplicando correctamente y se muestran en mi consola de desarrollador y en Postman. Sin embargo, el problema es que, mientras pasa la comprobación de Access-Control-Allow-Origin , el navegador web arroja un ataque de ira (creo) Access-Control-Allow-Headers indicando 415 (Unsupported Media Type)

Entonces el método de fuerza bruta tampoco funciona


Finalmente

¿Alguien ha conseguido esto para trabajar y podría echar una mano, o simplemente ser capaz de apuntarme en la dirección correcta?


EDITAR

Entonces, para hacer que las llamadas API pasaran, tuve que dejar de usar JQuery y cambiar a un formato Pure Javascript XMLHttpRequest .

Intento 1

Logré hacer que Microsoft.AspNetCore.Cors funcionara siguiendo la respuesta de MindingData, excepto en el Método de Configure coloca la app.UseCors antes de la app.UseMvc .

Además, cuando se mezcla con las options.AllowAnyOrigin() solución de API de Javascript, options.AllowAnyOrigin() para compatibilidad con comodines también comenzó a funcionar.

Intento 2

Así que he logrado que funcione el bash 2 (fuerza bruta) … con la única excepción de que el comodín para Access-Control-Allow-Origin no funciona y, como tal, tengo que configurar manualmente los dominios que tienen acceso lo.

Obviamente no es ideal, ya que solo quiero que esta WebAPI se abra completamente para todos, pero al menos funciona para mí en un sitio aparte, lo que significa que es un comienzo.

 app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder() .AddDefaultSecurePolicy() .AddCustomHeader("Access-Control-Allow-Origin", "http://localhost:3000") .AddCustomHeader("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE") .AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type, Authorization")); 

Debido a que tiene una política CORS muy simple (Permitir todas las solicitudes del dominio XXX), no necesita hacerlo tan complicado. Intenta hacer lo siguiente primero (una implementación muy básica de CORS).

Si aún no lo has hecho, instala el paquete CORS nuget.

 Install-Package Microsoft.AspNetCore.Cors 

En el método ConfigureServices de su startup.cs, agregue los servicios CORS.

 public void ConfigureServices(IServiceCollection services) { services.AddCors(); } 

Luego, en su método Configure de su startup.cs, agregue lo siguiente:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseCors( options => options.WithOrigins("http://example.com").AllowAnyMethod() ); app.UseMvc(); } 

Ahora pruébalo. Las políticas son para cuando quieres diferentes políticas para diferentes acciones (por ejemplo, diferentes hosts o diferentes encabezados). Para su simple ejemplo, realmente no lo necesita. Comience con este sencillo ejemplo y ajuste a medida que lo necesite desde allí.

Lectura adicional: http://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/

  • En ConfigureServices, agregue services.AddCors(); ANTES de services.AddMvc ();
  • Agregar UseCors en Configurar

     app.UseCors(builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); app.UseMvc(); 

El punto principal es que agrega app.UseCors , antes de app.UseMvc() .

Asegúrese de declarar la funcionalidad CORS antes de MVC para que el middleware se active antes de que la interconexión de MVC obtenga el control y finalice la solicitud.

Creé mi propia clase de middleware que funcionó para mí, creo que hay algo mal con la clase .net core middleware

 public class CorsMiddleware { private readonly RequestDelegate _next; public CorsMiddleware(RequestDelegate next) { _next = next; } public Task Invoke(HttpContext httpContext) { httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*"); httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name"); httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS"); return _next(httpContext); } } // Extension method used to add the middleware to the HTTP request pipeline. public static class CorsMiddlewareExtensions { public static IApplicationBuilder UseCorsMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } } 

y lo usé de esta manera en startup.cs

 app.UseCorsMiddleware(); 

En mi caso, solo get solicitud funciona bien de acuerdo con la respuesta de MindingData. Para otros tipos de solicitud, debe escribir:

 app.UseCors(corsPolicyBuilder => corsPolicyBuilder.WithOrigins("http://localhost:3000") .AllowAnyMethod() .AllowAnyHeader() ); 

No olvides agregar .AllowAnyHeader()

intente agregar jQuery.support.cors = true; antes de la llamada de Ajax

También podría ser que los datos que envía a la API no sean congruentes,

intente agregar la siguiente función JSON

  var JSON = JSON || {}; // implement JSON.stringify serialization JSON.stringify = JSON.stringify || function (obj) { var t = typeof (obj); if (t != "object" || obj === null) { // simple data type if (t == "string") obj = '"' + obj + '"'; return String(obj); } else { // recurse array or object var n, v, json = [], arr = (obj && obj.constructor == Array); for (n in obj) { v = obj[n]; t = typeof (v); if (t == "string") v = '"' + v + '"'; else if (t == "object" && v !== null) v = JSON.stringify(v); json.push((arr ? "" : '"' + n + '":') + String(v)); } return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}"); } }; // implement JSON.parse de-serialization JSON.parse = JSON.parse || function (str) { if (str === "") str = '""'; eval("var p=" + str + ";"); return p; }; 

luego en sus datos: objeto cambiarlo a

  data: JSON.stringify({ username: username, password: password }), 

Según su comentario en la respuesta de MindingData, no tiene nada que ver con su CORS, está funcionando bien.

Su acción de controlador está devolviendo los datos incorrectos. HttpCode 415 significa “Tipo de medio no admitido”. Esto sucede cuando pasa el formato incorrecto al controlador (es decir, XML a un controlador que solo acepta json) o cuando devuelve un tipo incorrecto (devuelve Xml en un controlador que se declara que solo devuelve xml).

Para más tarde, verifique la existencia del atributo [Produces("...")] en su acción

Para ampliar la respuesta de user8266077 , encontré que aún necesitaba suministrar respuesta OPTIONS para solicitudes de verificación previa en .NET Core 2.1-preview para mi caso de uso:

 // https://stackoverflow.com/a/45844400 public class CorsMiddleware { private readonly RequestDelegate _next; public CorsMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); context.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); // Added "Accept-Encoding" to this list context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Accept-Encoding, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name"); context.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS"); // New Code Starts here if (context.Request.Method == "OPTIONS") { context.Response.StatusCode = (int)HttpStatusCode.OK; await context.Response.WriteAsync(string.Empty); } // New Code Ends here await _next(context); } } 

y luego habilitó el middleware como tal en Startup.cs

 public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMiddleware(typeof(CorsMiddleware)); // ... other middleware inclusion such as ErrorHandling, Caching, etc app.UseMvc(); }