Compartir vía


Habilitar solicitudes entre orígenes (CORS) en ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulta la versión .NET 8 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulta la Directiva de soporte técnico de .NET y .NET Core. Para la versión actual, consulta la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulta la versión .NET 8 de este artículo.

Por Rick Anderson y Kirk Larkin

En este artículo se muestra cómo Cross-Origin Resource Sharing (CORS o Uso compartido de recursos entre orígenes) se habilita en una aplicación ASP.NET Core.

La seguridad del explorador evita que una página web realice solicitudes a un dominio diferente del que atendió a dicha página web. Esta restricción se denomina directiva de mismo origen. La directiva de mismo origen evita que un sitio malintencionado lea información confidencial de otro sitio. A veces, es posible que quiera permitir que otros sitios realicen solicitudes entre orígenes a la aplicación. Para obtener más información, consulte el artículo CORS de Mozilla.

Intercambio de recursos de origen cruzado (CORS):

  • Es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
  • No es una característica de seguridad, CORS relaja la seguridad. Una API no es más segura al permitir CORS. Para más información, vea Funcionamiento de CORS.
  • Permite explícitamente a un servidor a permitir algunas solicitudes de origen cruzado y rechazar otras.
  • Es más seguro y flexible que las técnicas anteriores, como JSONP.

Vea o descargue el código de ejemplo (cómo descargarlo)

Mismo origen

Dos URL tienen el mismo origen si tienen esquemas, hosts y puertos idénticos (RFC 6454).

Estas dos direcciones URL tienen el mismo origen:

  • https://example.com/foo.html
  • https://example.com/bar.html

Estas direcciones URL tienen orígenes diferentes a las dos direcciones URL anteriores:

  • https://example.net: Dominio diferente
  • https://contoso.example.com/foo.html: Subdominio diferente
  • http://example.com/foo.html: Esquema diferente
  • https://example.com:9000/foo.html: Puerto diferente

Habilitación de CORS

Existen tres formas de habilitar CORS:

El uso del atributo [EnableCors] con una directiva con nombre proporciona el mejor control para limitar los puntos finales que admiten CORS.

Advertencia

UseCors debe llamarse en el orden correcto. Para obtener más información vea Orden de Middleware. Por ejemplo, UseCors debe llamarse antes de UseResponseCaching cuando se utiliza UseResponseCaching.

Cada ennfoque se detalla en las secciones siguientes.

CORS con directivas con nombre y middleware

El middleware de CORS controla las solicitudes entre orígenes. El código siguiente aplica una directiva de CORS a todos los puntos de conexión de la aplicación con los orígenes especificados:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

Con el enrutamiento de puntos de conexión, el middleware de CORS debe configurarse para ejecutarse entre las llamadas a UseRouting y UseEndpoints.

La AddCors llamada al método añade servicios CORS al contenedor de servicios de la aplicación:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener más información, consulte Opciones de directiva CORS en este documento.

Los CorsPolicyBuilder métodos se pueden encadenar, como se muestra en el código siguiente:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Nota: La URL especificada no debe contener una barra diagonal (/). Si la dirección URL finaliza con /, la comparación devuelve false y no se devuelve ningún encabezado.

Orden de UseCors y UseStaticFiles

Normalmente, UseStaticFiles se llama a antes de UseCors. Las aplicaciones que usan JavaScript para recuperar archivos estáticos entre sitios deben llamar a UseCors antes de UseStaticFiles.

CORS con directivas predeterminadas y middleware

El código resaltado siguiente habilita la directiva CORS predeterminada:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior aplica la directiva CORS predeterminada a todos los puntos de conexión del controlador.

Habilitación de CORS con enrutamiento de punto de conexión

Con el enrutamiento de punto final, CORS se puede habilitar por punto final utilizando el RequireCors conjunto de métodos de extensión:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

En el código anterior:

  • app.UseCors agrega el middleware CORS. Debido a que no se ha configurado una política predeterminada, app.UseCors() por sí solo no habilita CORS.
  • Los /echo y puntos de conexión del controlador permiten solicitudes entre orígenes mediante la directiva especificada.
  • Los /echo2 y Razor puntos de conexión de páginas no permiten solicitudes de origen cruzado porque no se especificó ninguna política predeterminada.

El [DisableCors]atributo no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final con RequireCors.

Consulte Probar CORS con el atributo [EnableCors] y el método RequireCors para obtener instrucciones sobre cómo probar código similar al anterior.

Habilitación de CORS con atributos

Habilitar CORS con el atributo [EnableCors] y aplicar una directiva con nombre solo a los puntos de conexión que requieren CORS proporcionan el mejor control.

El atributo [EnableCors] proporciona una alternativa a aplicar CORS globalmente. El [EnableCors] atributo habilita CORS para los puntos de conexión seleccionados, en lugar de todos los puntos de conexión:

  • [EnableCors] especifica la política predeterminada.
  • [EnableCors("{Policy String}")] especifica una directiva guardada.

El atributo [EnableCors] se puede aplicar a:

  • Razor Página PageModel
  • Controlador
  • Método de acción del controlador

Se pueden aplicar diferentes directivas a controladores, modelos de página o métodos de acción con el atributo [EnableCors]. Cuando el atributo [EnableCors] se aplica a un controlador, modelo de página o método de acción, y CORS está habilitado en middleware, se aplican ambas directivas. Se recomienda combinar directivas. Use el el atributo o middleware [EnableCors], no ambos, en la misma aplicación.

El código siguiente aplica una directiva diferente a cada método:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

El siguiente código crea dos políticas CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener el mejor control de la limitación de solicitudes CORS:

El código de la sección siguiente cumple la lista anterior.

Deshabilitación de CORS

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final .

El siguiente código define la política CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

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

app.Run();

El código siguiente deshabilita CORS para la GetValues2 acción:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El código anterior:

Consulte Probar CORS para obtener instrucciones sobre cómo probar el código anterior.

Opciones de directiva CORS

En esta sección se describen las distintas opciones que se pueden establecer en una directiva de CORS:

AddPolicy es llamado por Program.cs. Para algunas opciones, puede resultar útil leer primero la sección Funcionamiento de CORS .

Establecimiento de los orígenes permitidos

AllowAnyOrigin: Permite solicitudes CORS desde todos los orígenes con cualquier esquema (http o https). AllowAnyOrigin no es seguro porque cualquier sitio web puede realizar solicitudes entre orígenes a la aplicación.

Nota

Especificar AllowAnyOrigin y AllowCredentials es una configuración no segura y puede dar lugar a una falsificación de solicitud entre sitios. El servicio CORS devuelve una respuesta CORS no válida cuando una aplicación está configurada con ambos métodos.

AllowAnyOrigin afecta a las solicitudes preparatorias y al Access-Control-Allow-Origin encabezado. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

SetIsOriginAllowedToAllowWildcardSubdomains: establece la IsOriginAllowed propiedad de la directiva en una función que permite que los orígenes coincidan con un dominio comodín configurado al evaluar si se permite el origen.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Establecimiento de los métodos HTTP permitidos

AllowAnyMethod:

  • Permite cualquier método HTTP:
  • Afecta a las solicitudes preparatorias y al encabezado Access-Control-Allow-Methods. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Establecimiento de los encabezados de solicitud permitidos

Para permitir que se envíen encabezados específicos en una solicitud CORS, llamada encabezados de solicitud de autor , llame a WithHeaders y especifique los encabezados permitidos:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llama a AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader afecta a las solicitudes de preventa y al encabezado Access-Control-Request-Headers . Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Una coincidencia de política de CORS Middleware con encabezados específicos especificados por WithHeaders solo es posible cuando los encabezados enviados en Access-Control-Request-Headers coinciden exactamente con los encabezados indicados en WithHeaders.

Por ejemplo, considere la posibilidad de configurar una aplicación como se indica a continuación:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware rechaza la solicitud preparatoria con el siguiente encabezado de solicitud porque Content-Language (HeaderNames.ContentLanguage ) no aparece en WithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

La aplicación devuelve una respuesta 200 OK pero no devuelve los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes.

Establecer los encabezados de respuesta expuestos

De forma predeterminada, el explorador no expone todos los encabezados de respuesta a la aplicación. Para obtener más información, consulte W3C Cross-Origin Resource Sharing (Terminology): Simple Response Header.

Los encabezados de respuesta que están disponibles de forma predeterminada son:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La especificación CORS llama a estos encabezados de respuesta simples. Para que otros encabezados estén disponibles para la aplicación, llame a WithExposedHeaders:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Credenciales en solicitudes entre orígenes

Las credenciales requieren un control especial en una solicitud CORS. De forma predeterminada, el navegador no envía credenciales con una solicitud de origen cruzado. Las credenciales incluyen cookies y esquemas de autenticación HTTP. Para enviar credenciales con una solicitud de origen cruzado, el cliente debe establecer XMLHttpRequest.withCredentials en true.

Uso XMLHttpRequest directo:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Uso de jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Uso de la Fetch API:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

El servidor debe permitir las credenciales. Para permitir credenciales de origen cruzado, llame a AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La respuesta HTTP incluye un encabezado Access-Control-Allow-Credentials, que le dice al navegador que el servidor permite credenciales para una solicitud de origen cruzado.

Si el explorador envía credenciales, pero la respuesta no incluye un encabezado válido Access-Control-Allow-Credentials, el explorador no expone la respuesta a la aplicación y se produce un error en la solicitud entre orígenes.

Permitir credenciales entre orígenes es un riesgo de seguridad. Un sitio web de otro dominio puede enviar las credenciales de un usuario que ha iniciado sesión a la aplicación en nombre del usuario sin el conocimiento del usuario.

La especificación CORS también indica que establecer orígenes en "*" (todos los orígenes) no es válido si el Access-Control-Allow-Credentials encabezado está presente.

Solicitud preparatoria

Para algunas solicitudes CORS, el navegador envía una solicitud adicional de OPTIONS antes de realizar la solicitud real. Esta solicitud se denomina solicitud preparatoria. El navegador puede omitir la solicitud preparatoria si se cumplen todas las siguientes condiciones:

  • El método de solicitud es GET, HE0AD o POST.
  • La aplicación no establece encabezados de solicitud que no sean Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID.
  • El encabezado Content-Type, si está configurado, tiene uno de los siguientes valores:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La regla sobre encabezados de solicitud establecida para la solicitud del cliente se aplica a los encabezados que la aplicación establece llamando a setRequestHeader en el objeto XMLHttpRequest. La especificación CORS llama a estos encabezados de solicitud de autor. La regla no se aplica a los encabezados que el explorador puede establecer, como User-Agent, Hosto Content-Length.

Nota:

En este artículo se incluyen las direcciones URL creadas al implementar el código de ejemplo en dos sitios web de Azure, https://cors3.azurewebsites.net y https://cors.azurewebsites.net.

La siguiente es una respuesta de ejemplo similar a la solicitud preparatoria realizada desde el botón [Put test] en la sección Test CORS de este documento.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La solicitud preparatoria utiliza el método OPCIONES HTTP . Puede incluir los siguientes encabezados:

  • Access-Control-Request-Method: método HTTP que se usará para la solicitud real.
  • Access-Control-Request-Headers: una lista de encabezados de solicitud que la aplicación establece en la solicitud real. Como se indicó anteriormente, esto no incluye encabezados que establece el explorador, como User-Agent.

Si se rechaza la solicitud de preventa, la aplicación devuelve una respuesta 200 OK pero no establece los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes. Para obtener un ejemplo de una solicitud preparatoria denegada, consulte la sección Probar CORS de este documento.

Con las herramientas F12, la aplicación de consola muestra un error similar a uno de los siguientes, según el explorador:

  • Firefox: solicitud de origen cruzado bloqueada: la política del mismo origen no permite leer el recurso remoto en https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motivo: la solicitud CORS no se realizó correctamente). Más información
  • Chromium basado: la directiva de CORS ha bloqueado el acceso para capturar en "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" desde el origenhttps://cors3.azurewebsites.net: La respuesta a la solicitud preparatoria no pasa la comprobación de control de acceso: no hay ningún encabezado "Access-Control-Allow-Origin" presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Para permitir encabezados específicos, llame a WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llama a AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Los navegadores no son consistentes en la forma en que configuran Access-Control-Request-Headers. Si alguna de las dos opciones:

  • Los encabezados se establecen en cualquier otro valor que no sea "*"
  • AllowAnyHeader se llama a : incluya al menos Accept, Content-Typey Origin, además de los encabezados personalizados que quiera admitir.

Código de solicitud de preparatoria automático

Cuando se aplica la directiva CORS:

  • Globalmente llamando a app.UseCors en Program.cs.
  • Uso del atributo [EnableCors].

ASP.NET Core responde a la solicitud preparatoria de OPTIONS.

La sección CORS de prueba de este documento muestra este comportamiento.

Atributo [HttpOptions] para solicitudes preparatorias

Cuando CORS está habilitado con la directiva adecuada, ASP.NET Core suele responder automáticamente a las solicitudes previas de CORS.

El código siguiente usa el atributo [HttpOptions] para crear puntos de conexión para las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consulte Prueba CORS con el atributo [EnableCors] y el método RequireCors para obtener instrucciones sobre cómo probar el código anterior.

Establecer la hora de expiración previa

El encabezado Access-Control-Max-Age especifica cuánto tiempo se puede almacenar en caché la respuesta a la solicitud preparatoria. Para establecer este encabezado, llame a SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Habilitación de CORS en un punto de conexión

Cómo funciona la CORS

Esta sección describe lo que sucede en una solicitud CORS a nivel de los mensajes HTTP.

  • CORS no es una función de seguridad. El uso compartido de recursos entre orígenes (CORS) es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
    • Por ejemplo, un actor malicioso podría usar Cross-Site Scripting (XSS) contra su sitio y ejecutar una solicitud entre sitios a su sitio habilitado para CORS para robar información.
  • Una API no es más segura al permitir CORS.
    • Es necesario que el cliente (explorador) aplique CORS. El servidor ejecuta la solicitud y devuelve la respuesta, es el cliente que devuelve un error y bloquea la respuesta. Por ejemplo, cualquiera de las siguientes herramientas mostrará la respuesta del servidor:
  • Es una forma de que un servidor permita a los navegadores ejecutar una solicitud XHR o Fetch API de origen cruzado que, de lo contrario, estaría prohibida.
    • Los navegadores sin CORS no pueden hacer solicitudes de origen cruzado. Antes de CORS, se usaba JSONP para eludir esta restricción. JSONP no utiliza XHR, utiliza la etiqueta <script> para recibir la respuesta. Se permite cargar scripts entre orígenes.

La especificación CORS introdujo varios encabezados HTTP nuevos que permiten solicitudes de origen cruzado. Si un explorador admite CORS, establece estos encabezados automáticamente para las solicitudes entre orígenes. El código JavaScript personalizado no es necesario para habilitar CORS.

El siguiente es un ejemplo de una solicitud de origen cruzado desde el botón de prueba Valores a https://cors1.azurewebsites.net/api/values. El encabezado Origin:

  • Proporciona el dominio del sitio que realiza la solicitud.
  • Es necesario y debe ser diferente del host.

Encabezados generales

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Encabezados de respuesta

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

En las solicitudes OPTIONS, el servidor establece el encabezado Encabezados de respuesta Access-Control-Allow-Origin: {allowed origin} en la respuesta. Por ejemplo, en el código de ejemplo, la solicitud OPTIONS del botón Delete [EnableCors] contiene los siguientes encabezados:

Encabezados generales

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Encabezados de respuesta

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

En los encabezados de respuesta anteriores, el servidor establece el encabezado Access-Control-Allow-Origin en la respuesta. El valor https://cors1.azurewebsites.net de este encabezado coincide con el encabezado Origin de la solicitud.

Si se llama aAllowAnyOrigin, el Access-Control-Allow-Origin: * se devuelve el valor comodín . AllowAnyOrigin permite cualquier origen.

Si la respuesta no incluye el encabezado Access-Control-Allow-Origin, la solicitud de origen cruzado falla. En concreto, el navegador no permite la solicitud. Incluso si el servidor devuelve una respuesta correcta, el explorador no hace que la respuesta esté disponible para la aplicación cliente.

El redireccionamiento HTTP a HTTPS provoca ERR_INVALID_REDIRECT en la solicitud de preparatoria de CORS

Las solicitudes a un punto de conexión usando HTTP que son redirigidas a HTTPS por UseHttpsRedirection fallan con ERR_INVALID_REDIRECT on the CORS preflight request.

Los proyectos de API pueden rechazar solicitudes HTTP en lugar de usar UseHttpsRedirection para redirigir solicitudes a HTTPS.

CORS en IIS

Al implementar en IIS, CORS debe ejecutarse antes de la autenticación de Windows si el servidor no está configurado para permitir el acceso anónimo. Para admitir este escenario, el módulo CORS de IIS debe instalarse y configurarse para la aplicación.

Prueba de CORS

La descarga de muestra tiene código para probar CORS. Vea cómo descargarlo. El ejemplo es un proyecto de API con Razor páginas agregadas:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Advertencia

WithOrigins("https://localhost:<port>"); solo se debería usar para probar una aplicación de ejemplo similar al código de ejemplo de descarga.

A continuación ValuesController se proporcionan los puntos de conexión para las pruebas:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

Pruebe el código de ejemplo anterior mediante uno de los métodos siguientes:

  • Ejecute el ejemplo con dotnet run utilizando la URL predeterminada de https://localhost:5001.
  • Ejecute el ejemplo desde Visual Studio con el puerto establecido en 44398 para una URL de https://localhost:44398.

Uso de un explorador con las herramientas F12:

  • Seleccione el botón Valores y revise los encabezados en la pestaña Red.

  • Seleccione el botón probar PUT. Consulte Mostrar solicitudes de OPCIONES para obtener instrucciones sobre cómo mostrar la solicitud de OPCIONES. La prueba PUT crea dos solicitudes, una solicitud preparatoria de OPCIONES y la solicitud PUT.

  • Seleccione el GetValues2 [DisableCors] botón para desencadenar una solicitud CORS con error. Como se mencionó en el documento, la respuesta devuelve 200 correcto, pero no se realiza la solicitud CORS. Seleccione la pestaña Consola para ver el error de CORS. Dependiendo del navegador, se muestra un error similar al siguiente:

    El acceso a la obtención en 'https://cors1.azurewebsites.net/api/values/GetValues2' desde el origen 'https://cors3.azurewebsites.net' ha sido bloqueado por la política de CORS: no hay encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Los puntos finales habilitados para CORS se pueden probar con una herramienta, como curl o Fiddler. Cuando se utiliza una herramienta, el origen de la solicitud especificada por el encabezado Origin debe diferir del host que recibe la solicitud. Si la solicitud no es de origen cruzado según el valor del encabezado Origin:

  • No es necesario que el middleware de CORS procese la solicitud.
  • Los encabezados CORS no se devuelven en la respuesta.

El siguiente comando usa curl para emitir una solicitud OPTIONS con información:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Prueba de CORS con el atributo [EnableCors] y el método RequireCors

Considere el siguiente código que utiliza enrutamiento de punto final para habilitar CORS por punto final utilizando RequireCors:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Observe que solo el /echo punto de conexión está utilizando el RequireCors para permitir solicitudes de origen cruzado utilizando la directiva especificada. Los controladores siguientes habilitan CORS mediante el atributo [EnableCors].

A continuación TodoItems1Controller se proporcionan los puntos de conexión para las pruebas:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Los botones Delete [EnableCors] y GET [EnableCors] tienen éxito, porque los puntos finales tienen [EnableCors] y responden a las solicitudes preparatorias. Se produce un error en los demás puntos de conexión. El botón GET falla, porque el JavaScript envía:

 headers: {
      "Content-Type": "x-custom-header"
 },

A continuación TodoItems2Controller se proporcionan puntos de conexión similares, pero se incluye código explícito para responder a las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

El código anterior se puede probar mediante la implementación del ejemplo en Azure. En la lista desplegable Controlador, seleccione Preflight y, a continuación, Establecer controlador. Todas las llamadas CORS a los puntos finales TodoItems2Controller tienen éxito.

Recursos adicionales

Por Rick Anderson y Kirk Larkin

En este artículo se muestra cómo habilitar CORS en una aplicación de ASP.NET Core.

La seguridad del explorador evita que una página web realice solicitudes a un dominio diferente del que atendió a dicha página web. Esta restricción se denomina directiva de mismo origen. La directiva de mismo origen evita que un sitio malintencionado lea información confidencial de otro sitio. A veces, es posible que quiera permitir que otros sitios realicen solicitudes entre orígenes a la aplicación. Para obtener más información, consulte el artículo CORS de Mozilla.

Intercambio de recursos de origen cruzado (CORS):

  • Es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
  • No es una característica de seguridad, CORS relaja la seguridad. Una API no es más segura al permitir CORS. Para más información, vea Funcionamiento de CORS.
  • Permite explícitamente a un servidor a permitir algunas solicitudes de origen cruzado y rechazar otras.
  • Es más seguro y flexible que las técnicas anteriores, como JSONP.

Vea o descargue el código de ejemplo (cómo descargarlo)

Mismo origen

Dos URL tienen el mismo origen si tienen esquemas, hosts y puertos idénticos (RFC 6454).

Estas dos direcciones URL tienen el mismo origen:

  • https://example.com/foo.html
  • https://example.com/bar.html

Estas direcciones URL tienen orígenes diferentes a las dos direcciones URL anteriores:

  • https://example.net: Dominio diferente
  • https://www.example.com/foo.html: Subdominio diferente
  • http://example.com/foo.html: Esquema diferente
  • https://example.com:9000/foo.html: Puerto diferente

Habilitación de CORS

Existen tres formas de habilitar CORS:

El uso del atributo [EnableCors] con una directiva con nombre proporciona el mejor control para limitar los puntos finales que admiten CORS.

Advertencia

UseCors debe llamarse en el orden correcto. Para obtener más información vea Orden de Middleware. Por ejemplo, UseCors debe llamarse antes de UseResponseCaching cuando se utiliza UseResponseCaching.

Cada ennfoque se detalla en las secciones siguientes.

CORS con directivas con nombre y middleware

El middleware de CORS controla las solicitudes entre orígenes. El código siguiente aplica una directiva de CORS a todos los puntos de conexión de la aplicación con los orígenes especificados:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

Con el enrutamiento de puntos de conexión, el middleware de CORS debe configurarse para ejecutarse entre las llamadas a UseRouting y UseEndpoints.

La AddCors llamada al método añade servicios CORS al contenedor de servicios de la aplicación:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener más información, consulte Opciones de directiva CORS en este documento.

Los CorsPolicyBuilder métodos se pueden encadenar, como se muestra en el código siguiente:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Nota: La URL especificada no debe contener una barra diagonal (/). Si la dirección URL finaliza con /, la comparación devuelve false y no se devuelve ningún encabezado.

Advertencia

UseCors debe colocarse después UseRouting y antes de UseAuthorization. Esto es para asegurarse de que los encabezados CORS se incluyen en la respuesta para las llamadas autorizadas y no autorizadas.

Orden de UseCors y UseStaticFiles

Normalmente, UseStaticFiles se llama a antes de UseCors. Las aplicaciones que usan JavaScript para recuperar archivos estáticos entre sitios deben llamar a UseCors antes de UseStaticFiles.

CORS con directivas predeterminadas y middleware

El código resaltado siguiente habilita la directiva CORS predeterminada:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior aplica la directiva CORS predeterminada a todos los puntos de conexión del controlador.

Habilitación de CORS con enrutamiento de punto de conexión

Con el enrutamiento de punto final, CORS se puede habilitar por punto final utilizando el RequireCors conjunto de métodos de extensión:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

En el código anterior:

  • app.UseCors agrega el middleware CORS. Debido a que no se ha configurado una política predeterminada, app.UseCors() por sí solo no habilita CORS.
  • Los /echo y puntos de conexión del controlador permiten solicitudes entre orígenes mediante la directiva especificada.
  • Los /echo2 y Razor puntos de conexión de páginas no permiten solicitudes de origen cruzado porque no se especificó ninguna política predeterminada.

El [DisableCors]atributo no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final con RequireCors.

En ASP.NET Core 7.0, el [EnableCors] atributo debe pasar un parámetro o una advertencia ASP0023 a partir de una coincidencia ambigua en la ruta. ASP.NET Core 8.0 y versiones posteriores no genera la ASP0023 advertencia.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Consulte Probar CORS con el atributo [EnableCors] y el método RequireCors para obtener instrucciones sobre cómo probar código similar al anterior.

Habilitación de CORS con atributos

Habilitar CORS con el atributo [EnableCors] y aplicar una directiva con nombre solo a los puntos de conexión que requieren CORS proporcionan el mejor control.

El atributo [EnableCors] proporciona una alternativa a aplicar CORS globalmente. El [EnableCors] atributo habilita CORS para los puntos de conexión seleccionados, en lugar de todos los puntos de conexión:

  • [EnableCors] especifica la política predeterminada.
  • [EnableCors("{Policy String}")] especifica una directiva guardada.

El atributo [EnableCors] se puede aplicar a:

  • Razor Página PageModel
  • Controlador
  • Método de acción del controlador

Se pueden aplicar diferentes directivas a controladores, modelos de página o métodos de acción con el atributo [EnableCors]. Cuando el atributo [EnableCors] se aplica a un controlador, modelo de página o método de acción, y CORS está habilitado en middleware, se aplican ambas directivas. Se recomienda combinar directivas. Use el el atributo o middleware [EnableCors], no ambos, en la misma aplicación.

El código siguiente aplica una directiva diferente a cada método:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

El siguiente código crea dos políticas CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener el mejor control de la limitación de solicitudes CORS:

El código de la sección siguiente cumple la lista anterior.

Deshabilitación de CORS

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final .

El siguiente código define la política CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

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

app.Run();

El código siguiente deshabilita CORS para la GetValues2 acción:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El código anterior:

Consulte Probar CORS para obtener instrucciones sobre cómo probar el código anterior.

Opciones de directiva CORS

En esta sección se describen las distintas opciones que se pueden establecer en una directiva de CORS:

AddPolicy es llamado por Program.cs. Para algunas opciones, puede resultar útil leer primero la sección Funcionamiento de CORS .

Establecimiento de los orígenes permitidos

AllowAnyOrigin: Permite solicitudes CORS desde todos los orígenes con cualquier esquema (http o https). AllowAnyOrigin no es seguro porque cualquier sitio web puede realizar solicitudes entre orígenes a la aplicación.

Nota

Especificar AllowAnyOrigin y AllowCredentials es una configuración no segura y puede dar lugar a una falsificación de solicitud entre sitios. El servicio CORS devuelve una respuesta CORS no válida cuando una aplicación está configurada con ambos métodos.

AllowAnyOrigin afecta a las solicitudes preparatorias y al Access-Control-Allow-Origin encabezado. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

SetIsOriginAllowedToAllowWildcardSubdomains: establece la IsOriginAllowed propiedad de la directiva en una función que permite que los orígenes coincidan con un dominio comodín configurado al evaluar si se permite el origen.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Establecimiento de los métodos HTTP permitidos

AllowAnyMethod:

  • Permite cualquier método HTTP:
  • Afecta a las solicitudes preparatorias y al encabezado Access-Control-Allow-Methods. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Establecimiento de los encabezados de solicitud permitidos

Para permitir que se envíen encabezados específicos en una solicitud CORS, llamada encabezados de solicitud de autor , llame a WithHeaders y especifique los encabezados permitidos:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader afecta a las solicitudes de preventa y al encabezado Access-Control-Request-Headers . Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Una coincidencia de política de CORS Middleware con encabezados específicos especificados por WithHeaders solo es posible cuando los encabezados enviados en Access-Control-Request-Headers coinciden exactamente con los encabezados indicados en WithHeaders.

Por ejemplo, considere la posibilidad de configurar una aplicación como se indica a continuación:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware rechaza la solicitud preparatoria con el siguiente encabezado de solicitud porque Content-Language (HeaderNames.ContentLanguage ) no aparece en WithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

La aplicación devuelve una respuesta 200 OK pero no devuelve los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes.

Establecer los encabezados de respuesta expuestos

De forma predeterminada, el explorador no expone todos los encabezados de respuesta a la aplicación. Para obtener más información, consulte W3C Cross-Origin Resource Sharing (Terminology): Simple Response Header.

Los encabezados de respuesta que están disponibles de forma predeterminada son:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La especificación CORS llama a estos encabezados de respuesta simples. Para que otros encabezados estén disponibles para la aplicación, llame a WithExposedHeaders:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Credenciales en solicitudes entre orígenes

Las credenciales requieren un control especial en una solicitud CORS. De forma predeterminada, el navegador no envía credenciales con una solicitud de origen cruzado. Las credenciales incluyen cookies y esquemas de autenticación HTTP. Para enviar credenciales con una solicitud de origen cruzado, el cliente debe establecer XMLHttpRequest.withCredentials en true.

Uso XMLHttpRequest directo:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Uso de jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Uso de la Fetch API:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

El servidor debe permitir las credenciales. Para permitir credenciales de origen cruzado, llame a AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La respuesta HTTP incluye un encabezado Access-Control-Allow-Credentials, que le dice al navegador que el servidor permite credenciales para una solicitud de origen cruzado.

Si el explorador envía credenciales, pero la respuesta no incluye un encabezado válido Access-Control-Allow-Credentials, el explorador no expone la respuesta a la aplicación y se produce un error en la solicitud entre orígenes.

Permitir credenciales entre orígenes es un riesgo de seguridad. Un sitio web de otro dominio puede enviar las credenciales de un usuario que ha iniciado sesión a la aplicación en nombre del usuario sin el conocimiento del usuario.

La especificación CORS también indica que establecer orígenes en "*" (todos los orígenes) no es válido si el Access-Control-Allow-Credentials encabezado está presente.

Solicitud preparatoria

Para algunas solicitudes CORS, el navegador envía una solicitud adicional de OPTIONS antes de realizar la solicitud real. Esta solicitud se denomina solicitud preparatoria. El navegador puede omitir la solicitud preparatoria si se cumplen todas las siguientes condiciones:

  • El método de solicitud es GET, HE0AD o POST.
  • La aplicación no establece encabezados de solicitud que no sean Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID.
  • El encabezado Content-Type, si está configurado, tiene uno de los siguientes valores:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La regla sobre encabezados de solicitud establecida para la solicitud del cliente se aplica a los encabezados que la aplicación establece llamando a setRequestHeader en el objeto XMLHttpRequest. La especificación CORS llama a estos encabezados de solicitud de autor. La regla no se aplica a los encabezados que el explorador puede establecer, como User-Agent, Hosto Content-Length.

La siguiente es una respuesta de ejemplo similar a la solicitud preparatoria realizada desde el botón [Put test] en la sección Test CORS de este documento.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La solicitud preparatoria utiliza el método OPCIONES HTTP . Puede incluir los siguientes encabezados:

Si se rechaza la solicitud de preventa, la aplicación devuelve una respuesta 200 OK pero no establece los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes. Para obtener un ejemplo de una solicitud preparatoria denegada, consulte la sección Probar CORS de este documento.

Con las herramientas F12, la aplicación de consola muestra un error similar a uno de los siguientes, según el explorador:

  • Firefox: solicitud de origen cruzado bloqueada: la política del mismo origen no permite leer el recurso remoto en https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motivo: la solicitud CORS no se realizó correctamente). Más información
  • Chromium basado: la directiva de CORS ha bloqueado el acceso para capturar en "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" desde el origenhttps://cors3.azurewebsites.net: La respuesta a la solicitud preparatoria no pasa la comprobación de control de acceso: no hay ningún encabezado "Access-Control-Allow-Origin" presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Para permitir encabezados específicos, llame a WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Los navegadores no son consistentes en la forma en que configuran Access-Control-Request-Headers. Si alguna de las dos opciones:

  • Los encabezados se establecen en cualquier otro valor que no sea "*"
  • AllowAnyHeader se llama a : incluya al menos Accept, Content-Typey Origin, además de los encabezados personalizados que quiera admitir.

Código de solicitud de preparatoria automático

Cuando se aplica la directiva CORS:

  • Globalmente llamando a app.UseCors en Program.cs.
  • Uso del atributo [EnableCors].

ASP.NET Core responde a la solicitud preparatoria de OPTIONS.

La sección CORS de prueba de este documento muestra este comportamiento.

Atributo [HttpOptions] para solicitudes preparatorias

Cuando CORS está habilitado con la directiva adecuada, ASP.NET Core suele responder automáticamente a las solicitudes previas de CORS.

El código siguiente usa el atributo [HttpOptions] para crear puntos de conexión para las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consulte Prueba CORS con el atributo [EnableCors] y el método RequireCors para obtener instrucciones sobre cómo probar el código anterior.

Establecer la hora de expiración previa

El encabezado Access-Control-Max-Age especifica cuánto tiempo se puede almacenar en caché la respuesta a la solicitud preparatoria. Para establecer este encabezado, llame a SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Habilitación de CORS en un punto de conexión

Cómo funciona la CORS

Esta sección describe lo que sucede en una solicitud CORS a nivel de los mensajes HTTP.

  • CORS no es una función de seguridad. El uso compartido de recursos entre orígenes (CORS) es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
    • Por ejemplo, un actor malicioso podría usar Cross-Site Scripting (XSS) contra su sitio y ejecutar una solicitud entre sitios a su sitio habilitado para CORS para robar información.
  • Una API no es más segura al permitir CORS.
    • Es necesario que el cliente (explorador) aplique CORS. El servidor ejecuta la solicitud y devuelve la respuesta, es el cliente que devuelve un error y bloquea la respuesta. Por ejemplo, cualquiera de las siguientes herramientas mostrará la respuesta del servidor:
  • Es una forma de que un servidor permita a los navegadores ejecutar una solicitud XHR o Fetch API de origen cruzado que, de lo contrario, estaría prohibida.
    • Los navegadores sin CORS no pueden hacer solicitudes de origen cruzado. Antes de CORS, se usaba JSONP para eludir esta restricción. JSONP no utiliza XHR, utiliza la etiqueta <script> para recibir la respuesta. Se permite cargar scripts entre orígenes.

La especificación CORS introdujo varios encabezados HTTP nuevos que permiten solicitudes de origen cruzado. Si un explorador admite CORS, establece estos encabezados automáticamente para las solicitudes entre orígenes. El código JavaScript personalizado no es necesario para habilitar CORS.

Selecciona el botón de prueba PUT de la muestra implementada. El encabezado Origin:

  • Proporciona el dominio del sitio que realiza la solicitud.
  • Es necesario y debe ser diferente del host.

Encabezados generales

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Encabezados de respuesta

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

En las solicitudes OPTIONS, el servidor establece el encabezado Encabezados de respuesta Access-Control-Allow-Origin: {allowed origin} en la respuesta. Por ejemplo, en el código de ejemplo, la solicitud OPTIONS del botón Delete [EnableCors] contiene los siguientes encabezados:

Encabezados generales

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Encabezados de respuesta

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

En los encabezados de respuesta anteriores, el servidor establece el encabezado Access-Control-Allow-Origin en la respuesta. El valor https://cors1.azurewebsites.net de este encabezado coincide con el encabezado Origin de la solicitud.

Si se llama aAllowAnyOrigin, el Access-Control-Allow-Origin: * se devuelve el valor comodín . AllowAnyOrigin permite cualquier origen.

Si la respuesta no incluye el encabezado Access-Control-Allow-Origin, la solicitud de origen cruzado falla. En concreto, el navegador no permite la solicitud. Incluso si el servidor devuelve una respuesta correcta, el explorador no hace que la respuesta esté disponible para la aplicación cliente.

El redireccionamiento HTTP a HTTPS provoca ERR_INVALID_REDIRECT en la solicitud de preparatoria de CORS

Las solicitudes a un punto de conexión usando HTTP que son redirigidas a HTTPS por UseHttpsRedirection fallan con ERR_INVALID_REDIRECT on the CORS preflight request.

Los proyectos de API pueden rechazar solicitudes HTTP en lugar de usar UseHttpsRedirection para redirigir solicitudes a HTTPS.

CORS en IIS

Al implementar en IIS, CORS debe ejecutarse antes de la autenticación de Windows si el servidor no está configurado para permitir el acceso anónimo. Para admitir este escenario, el módulo CORS de IIS debe instalarse y configurarse para la aplicación.

Prueba de CORS

La descarga de muestra tiene código para probar CORS. Vea cómo descargarlo. El ejemplo es un proyecto de API con Razor páginas agregadas:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Advertencia

WithOrigins("https://localhost:<port>"); solo se debería usar para probar una aplicación de ejemplo similar al código de ejemplo de descarga.

A continuación ValuesController se proporcionan los puntos de conexión para las pruebas:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

Pruebe el código de ejemplo anterior mediante uno de los métodos siguientes:

  • Ejecute el ejemplo con dotnet run utilizando la URL predeterminada de https://localhost:5001.
  • Ejecute el ejemplo desde Visual Studio con el puerto establecido en 44398 para una URL de https://localhost:44398.

Uso de un explorador con las herramientas F12:

  • Seleccione el botón Valores y revise los encabezados en la pestaña Red.

  • Seleccione el botón probar PUT. Consulte Mostrar solicitudes de OPCIONES para obtener instrucciones sobre cómo mostrar la solicitud de OPCIONES. La prueba PUT crea dos solicitudes, una solicitud preparatoria de OPCIONES y la solicitud PUT.

  • Seleccione el GetValues2 [DisableCors] botón para desencadenar una solicitud CORS con error. Como se mencionó en el documento, la respuesta devuelve 200 correcto, pero no se realiza la solicitud CORS. Seleccione la pestaña Consola para ver el error de CORS. Dependiendo del navegador, se muestra un error similar al siguiente:

    El acceso a la obtención en 'https://cors1.azurewebsites.net/api/values/GetValues2' desde el origen 'https://cors3.azurewebsites.net' ha sido bloqueado por la política de CORS: no hay encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Los puntos finales habilitados para CORS se pueden probar con una herramienta, como curl o Fiddler. Cuando se utiliza una herramienta, el origen de la solicitud especificada por el encabezado Origin debe diferir del host que recibe la solicitud. Si la solicitud no es de origen cruzado según el valor del encabezado Origin:

  • No es necesario que el middleware de CORS procese la solicitud.
  • Los encabezados CORS no se devuelven en la respuesta.

El siguiente comando usa curl para emitir una solicitud OPTIONS con información:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Prueba de CORS con el atributo [EnableCors] y el método RequireCors

Considere el siguiente código que utiliza enrutamiento de punto final para habilitar CORS por punto final utilizando RequireCors:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Observe que solo el /echo punto de conexión está utilizando el RequireCors para permitir solicitudes de origen cruzado utilizando la directiva especificada. Los controladores siguientes habilitan CORS mediante el atributo [EnableCors].

A continuación TodoItems1Controller se proporcionan los puntos de conexión para las pruebas:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Los botones Delete [EnableCors] y GET [EnableCors] tienen éxito, porque los puntos finales tienen [EnableCors] y responden a las solicitudes preparatorias. Se produce un error en los demás puntos de conexión. El botón GET falla, porque el JavaScript envía:

 headers: {
      "Content-Type": "x-custom-header"
 },

A continuación TodoItems2Controller se proporcionan puntos de conexión similares, pero se incluye código explícito para responder a las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

El código anterior se puede probar al implementar la muestra en Azure. En la lista desplegable Controlador, selecciona Preparatorio y después Configurar controlador. Todas las llamadas CORS a los puntos finales TodoItems2Controller tienen éxito.

Recursos adicionales

Por Rick Anderson y Kirk Larkin

En este artículo se muestra cómo habilitar CORS en una aplicación de ASP.NET Core.

La seguridad del explorador evita que una página web realice solicitudes a un dominio diferente del que atendió a dicha página web. Esta restricción se denomina directiva de mismo origen. La directiva de mismo origen evita que un sitio malintencionado lea información confidencial de otro sitio. A veces, es posible que quiera permitir que otros sitios realicen solicitudes entre orígenes a la aplicación. Para obtener más información, consulte el artículo CORS de Mozilla.

Intercambio de recursos de origen cruzado (CORS):

  • Es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
  • No es una característica de seguridad, CORS relaja la seguridad. Una API no es más segura al permitir CORS. Para más información, vea Funcionamiento de CORS.
  • Permite explícitamente a un servidor a permitir algunas solicitudes de origen cruzado y rechazar otras.
  • Es más seguro y flexible que las técnicas anteriores, como JSONP.

Vea o descargue el código de ejemplo (cómo descargarlo)

Mismo origen

Dos URL tienen el mismo origen si tienen esquemas, hosts y puertos idénticos (RFC 6454).

Estas dos direcciones URL tienen el mismo origen:

  • https://example.com/foo.html
  • https://example.com/bar.html

Estas direcciones URL tienen orígenes diferentes a las dos direcciones URL anteriores:

  • https://example.net: Dominio diferente
  • https://www.example.com/foo.html: Subdominio diferente
  • http://example.com/foo.html: Esquema diferente
  • https://example.com:9000/foo.html: Puerto diferente

Habilitación de CORS

Existen tres formas de habilitar CORS:

El uso del atributo [EnableCors] con una directiva con nombre proporciona el mejor control para limitar los puntos finales que admiten CORS.

Advertencia

UseCors debe llamarse en el orden correcto. Para obtener más información vea Orden de Middleware. Por ejemplo, UseCors debe llamarse antes de UseResponseCaching cuando se utiliza UseResponseCaching.

Cada ennfoque se detalla en las secciones siguientes.

CORS con directivas con nombre y middleware

El middleware de CORS controla las solicitudes entre orígenes. El código siguiente aplica una directiva de CORS a todos los puntos de conexión de la aplicación con los orígenes especificados:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior:

Con el enrutamiento de puntos de conexión, el middleware de CORS debe configurarse para ejecutarse entre las llamadas a UseRouting y UseEndpoints.

La AddCors llamada al método añade servicios CORS al contenedor de servicios de la aplicación:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener más información, consulte Opciones de directiva CORS en este documento.

Los CorsPolicyBuilder métodos se pueden encadenar, como se muestra en el código siguiente:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Nota: La URL especificada no debe contener una barra diagonal (/). Si la dirección URL finaliza con /, la comparación devuelve false y no se devuelve ningún encabezado.

Advertencia

UseCors debe colocarse después UseRouting y antes de UseAuthorization. Esto es para asegurarse de que los encabezados CORS se incluyen en la respuesta para las llamadas autorizadas y no autorizadas.

Orden de UseCors y UseStaticFiles

Normalmente, UseStaticFiles se llama a antes de UseCors. Las aplicaciones que usan JavaScript para recuperar archivos estáticos entre sitios deben llamar a UseCors antes de UseStaticFiles.

CORS con directivas predeterminadas y middleware

El código resaltado siguiente habilita la directiva CORS predeterminada:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código anterior aplica la directiva CORS predeterminada a todos los puntos de conexión del controlador.

Habilitación de CORS con enrutamiento de punto de conexión

La habilitación de CORS por punto de conexión usando RequireCors no admite solicitudes preparatorias automáticas. Para obtener más información, consulte este problema de GitHub y Pruebe CORS con enrutamiento de puntos de conexión y [HttpOptions].

Con el enrutamiento de punto final, CORS se puede habilitar por punto final utilizando el RequireCors conjunto de métodos de extensión:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

En el código anterior:

  • app.UseCors agrega el middleware CORS. Debido a que no se ha configurado una política predeterminada, app.UseCors() por sí solo no habilita CORS.
  • Los /echo y puntos de conexión del controlador permiten solicitudes entre orígenes mediante la directiva especificada.
  • Los /echo2 y Razor puntos de conexión de páginas no permiten solicitudes de origen cruzado porque no se especificó ninguna política predeterminada.

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final con RequireCors.

Consulte Prueba CORS con enrutamiento de punto final y [HttpOptions] para obtener instrucciones sobre el código de prueba similar al anterior.

Habilitación de CORS con atributos

Habilitar CORS con el atributo [EnableCors] y aplicar una directiva con nombre solo a los puntos de conexión que requieren CORS proporcionan el mejor control.

El atributo [EnableCors] proporciona una alternativa a aplicar CORS globalmente. El [EnableCors] atributo habilita CORS para los puntos de conexión seleccionados, en lugar de todos los puntos de conexión:

  • [EnableCors] especifica la política predeterminada.
  • [EnableCors("{Policy String}")] especifica una directiva guardada.

El atributo [EnableCors] se puede aplicar a:

  • Razor Página PageModel
  • Controlador
  • Método de acción del controlador

Se pueden aplicar diferentes directivas a controladores, modelos de página o métodos de acción con el atributo [EnableCors]. Cuando el atributo [EnableCors] se aplica a un controlador, modelo de página o método de acción, y CORS está habilitado en middleware, se aplican ambas directivas. Se recomienda combinar directivas. Use el el atributo o middleware [EnableCors], no ambos, en la misma aplicación.

El código siguiente aplica una directiva diferente a cada método:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

El siguiente código crea dos políticas CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener el mejor control de la limitación de solicitudes CORS:

El código de la sección siguiente cumple la lista anterior.

Deshabilitación de CORS

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final .

El siguiente código define la política CORS "MyPolicy":

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

El código siguiente deshabilita CORS para la GetValues2 acción:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El código anterior:

Consulte Probar CORS para obtener instrucciones sobre cómo probar el código anterior.

Opciones de directiva CORS

En esta sección se describen las distintas opciones que se pueden establecer en una directiva de CORS:

AddPolicy es llamado por Program.cs. Para algunas opciones, puede resultar útil leer primero la sección Funcionamiento de CORS .

Establecimiento de los orígenes permitidos

AllowAnyOrigin: Permite solicitudes CORS desde todos los orígenes con cualquier esquema (http o https). AllowAnyOrigin no es seguro porque cualquier sitio web puede realizar solicitudes entre orígenes a la aplicación.

Nota

Especificar AllowAnyOrigin y AllowCredentials es una configuración no segura y puede dar lugar a una falsificación de solicitud entre sitios. El servicio CORS devuelve una respuesta CORS no válida cuando una aplicación está configurada con ambos métodos.

AllowAnyOrigin afecta a las solicitudes preparatorias y al Access-Control-Allow-Origin encabezado. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

SetIsOriginAllowedToAllowWildcardSubdomains: establece la IsOriginAllowed propiedad de la directiva en una función que permite que los orígenes coincidan con un dominio comodín configurado al evaluar si se permite el origen.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Establecimiento de los métodos HTTP permitidos

AllowAnyMethod:

  • Permite cualquier método HTTP:
  • Afecta a las solicitudes preparatorias y al encabezado Access-Control-Allow-Methods. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Establecimiento de los encabezados de solicitud permitidos

Para permitir que se envíen encabezados específicos en una solicitud CORS, llamada encabezados de solicitud de autor , llame a WithHeaders y especifique los encabezados permitidos:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader afecta a las solicitudes de preventa y al encabezado Access-Control-Request-Headers . Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Una coincidencia de política de CORS Middleware con encabezados específicos especificados por WithHeaders solo es posible cuando los encabezados enviados en Access-Control-Request-Headers coinciden exactamente con los encabezados indicados en WithHeaders.

Por ejemplo, considere la posibilidad de configurar una aplicación como se indica a continuación:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware rechaza la solicitud preparatoria con el siguiente encabezado de solicitud porque Content-Language (HeaderNames.ContentLanguage ) no aparece en WithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

La aplicación devuelve una respuesta 200 OK pero no devuelve los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes.

Establecer los encabezados de respuesta expuestos

De forma predeterminada, el explorador no expone todos los encabezados de respuesta a la aplicación. Para obtener más información, consulte W3C Cross-Origin Resource Sharing (Terminology): Simple Response Header.

Los encabezados de respuesta que están disponibles de forma predeterminada son:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La especificación CORS llama a estos encabezados de respuesta simples. Para que otros encabezados estén disponibles para la aplicación, llame a WithExposedHeaders:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Credenciales en solicitudes entre orígenes

Las credenciales requieren un control especial en una solicitud CORS. De forma predeterminada, el navegador no envía credenciales con una solicitud de origen cruzado. Las credenciales incluyen cookies y esquemas de autenticación HTTP. Para enviar credenciales con una solicitud de origen cruzado, el cliente debe establecer XMLHttpRequest.withCredentials en true.

Uso XMLHttpRequest directo:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Uso de jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Uso de la Fetch API:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

El servidor debe permitir las credenciales. Para permitir credenciales de origen cruzado, llame a AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

La respuesta HTTP incluye un encabezado Access-Control-Allow-Credentials, que le dice al navegador que el servidor permite credenciales para una solicitud de origen cruzado.

Si el explorador envía credenciales, pero la respuesta no incluye un encabezado válido Access-Control-Allow-Credentials, el explorador no expone la respuesta a la aplicación y se produce un error en la solicitud entre orígenes.

Permitir credenciales entre orígenes es un riesgo de seguridad. Un sitio web de otro dominio puede enviar las credenciales de un usuario que ha iniciado sesión a la aplicación en nombre del usuario sin el conocimiento del usuario.

La especificación CORS también indica que establecer orígenes en "*" (todos los orígenes) no es válido si el Access-Control-Allow-Credentials encabezado está presente.

Solicitud preparatoria

Para algunas solicitudes CORS, el navegador envía una solicitud adicional de OPTIONS antes de realizar la solicitud real. Esta solicitud se denomina solicitud preparatoria. El navegador puede omitir la solicitud preparatoria si se cumplen todas las siguientes condiciones:

  • El método de solicitud es GET, HE0AD o POST.
  • La aplicación no establece encabezados de solicitud que no sean Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID.
  • El encabezado Content-Type, si está configurado, tiene uno de los siguientes valores:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La regla sobre encabezados de solicitud establecida para la solicitud del cliente se aplica a los encabezados que la aplicación establece llamando a setRequestHeader en el objeto XMLHttpRequest. La especificación CORS llama a estos encabezados de solicitud de autor. La regla no se aplica a los encabezados que el explorador puede establecer, como User-Agent, Hosto Content-Length.

La siguiente es una respuesta de ejemplo similar a la solicitud preparatoria realizada desde el botón [Put test] en la sección Test CORS de este documento.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La solicitud preparatoria utiliza el método OPCIONES HTTP . Puede incluir los siguientes encabezados:

Si se rechaza la solicitud de preventa, la aplicación devuelve una respuesta 200 OK pero no establece los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes. Para obtener un ejemplo de una solicitud preparatoria denegada, consulte la sección Probar CORS de este documento.

Con las herramientas F12, la aplicación de consola muestra un error similar a uno de los siguientes, según el explorador:

  • Firefox: solicitud de origen cruzado bloqueada: la política del mismo origen no permite leer el recurso remoto en https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motivo: la solicitud CORS no se realizó correctamente). Más información
  • Chromium basado: la directiva de CORS ha bloqueado el acceso para capturar en "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" desde el origenhttps://cors3.azurewebsites.net: La respuesta a la solicitud preparatoria no pasa la comprobación de control de acceso: no hay ningún encabezado "Access-Control-Allow-Origin" presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Para permitir encabezados específicos, llame a WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Los navegadores no son consistentes en la forma en que configuran Access-Control-Request-Headers. Si alguna de las dos opciones:

  • Los encabezados se establecen en cualquier otro valor que no sea "*"
  • AllowAnyHeader se llama a : incluya al menos Accept, Content-Typey Origin, además de los encabezados personalizados que quiera admitir.

Código de solicitud de preparatoria automático

Cuando se aplica la directiva CORS:

  • Globalmente llamando a app.UseCors en Program.cs.
  • Uso del atributo [EnableCors].

ASP.NET Core responde a la solicitud preparatoria de OPTIONS.

Habilitar CORS por punto final usando RequireCors actualmente no admite solicitudes automáticas preparatorias.

La sección CORS de prueba de este documento muestra este comportamiento.

Atributo [HttpOptions] para solicitudes preparatorias

Cuando CORS está habilitado con la directiva adecuada, ASP.NET Core suele responder automáticamente a las solicitudes previas de CORS. En algunos escenarios, es posible que esto no sea el caso. Por ejemplo, usando CORS con enrutamiento de punto final .

El código siguiente usa el atributo [HttpOptions] para crear puntos de conexión para las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consulte Prueba CORS con enrutamiento de punto final y [HttpOptions] para obtener instrucciones sobre cómo probar el código anterior.

Establecer la hora de expiración previa

El encabezado Access-Control-Max-Age especifica cuánto tiempo se puede almacenar en caché la respuesta a la solicitud preparatoria. Para establecer este encabezado, llame a SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Cómo funciona la CORS

Esta sección describe lo que sucede en una solicitud CORS a nivel de los mensajes HTTP.

  • CORS no es una función de seguridad. El uso compartido de recursos entre orígenes (CORS) es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
    • Por ejemplo, un actor malicioso podría usar Cross-Site Scripting (XSS) contra su sitio y ejecutar una solicitud entre sitios a su sitio habilitado para CORS para robar información.
  • Una API no es más segura al permitir CORS.
    • Es necesario que el cliente (explorador) aplique CORS. El servidor ejecuta la solicitud y devuelve la respuesta, es el cliente que devuelve un error y bloquea la respuesta. Por ejemplo, cualquiera de las siguientes herramientas mostrará la respuesta del servidor:
  • Es una forma de que un servidor permita a los navegadores ejecutar una solicitud XHR o Fetch API de origen cruzado que, de lo contrario, estaría prohibida.
    • Los navegadores sin CORS no pueden hacer solicitudes de origen cruzado. Antes de CORS, se usaba JSONP para eludir esta restricción. JSONP no utiliza XHR, utiliza la etiqueta <script> para recibir la respuesta. Se permite cargar scripts entre orígenes.

La especificación CORS introdujo varios encabezados HTTP nuevos que permiten solicitudes de origen cruzado. Si un explorador admite CORS, establece estos encabezados automáticamente para las solicitudes entre orígenes. El código JavaScript personalizado no es necesario para habilitar CORS.

El siguiente es un ejemplo de una solicitud de origen cruzado desde el botón de prueba Valores a https://cors1.azurewebsites.net/api/values. El encabezado Origin:

  • Proporciona el dominio del sitio que realiza la solicitud.
  • Es necesario y debe ser diferente del host.

Encabezados generales

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Encabezados de respuesta

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

En las solicitudes OPTIONS, el servidor establece el encabezado Encabezados de respuesta Access-Control-Allow-Origin: {allowed origin} en la respuesta. Por ejemplo, la solicitud del botónejemplo, Eliminar [EnableCors]OPTIONS desplegada contiene los siguientes encabezados:

Encabezados generales

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Encabezados de respuesta

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

En los encabezados de respuesta anteriores, el servidor establece el encabezado Access-Control-Allow-Origin en la respuesta. El valor https://cors1.azurewebsites.net de este encabezado coincide con el encabezado Origin de la solicitud.

Si se llama aAllowAnyOrigin, el Access-Control-Allow-Origin: * se devuelve el valor comodín . AllowAnyOrigin permite cualquier origen.

Si la respuesta no incluye el encabezado Access-Control-Allow-Origin, la solicitud de origen cruzado falla. En concreto, el navegador no permite la solicitud. Incluso si el servidor devuelve una respuesta correcta, el explorador no hace que la respuesta esté disponible para la aplicación cliente.

El redireccionamiento HTTP a HTTPS provoca ERR_INVALID_REDIRECT en la solicitud de preparatoria de CORS

Las solicitudes a un punto de conexión usando HTTP que son redirigidas a HTTPS por UseHttpsRedirection fallan con ERR_INVALID_REDIRECT on the CORS preflight request.

Los proyectos de API pueden rechazar solicitudes HTTP en lugar de usar UseHttpsRedirection para redirigir solicitudes a HTTPS.

Mostrar solicitudes OPTIONS

De forma predeterminada, los exploradores Chrome y Edge no muestran las solicitudes OPTIONS en la pestaña de red de las herramientas F12. Para mostrar las solicitudes OPTIONS en estos exploradores:

  • chrome://flags/#out-of-blink-cors o edge://flags/#out-of-blink-cors
  • deshabilite la marca.
  • Reiniciar.

Firefox muestra las solicitudes OPTIONS de forma predeterminada.

CORS en IIS

Al implementar en IIS, CORS debe ejecutarse antes de la autenticación de Windows si el servidor no está configurado para permitir el acceso anónimo. Para admitir este escenario, el módulo CORS de IIS debe instalarse y configurarse para la aplicación.

Prueba de CORS

La descarga de muestra tiene código para probar CORS. Vea cómo descargarlo. El ejemplo es un proyecto de API con Razor páginas agregadas:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Advertencia

WithOrigins("https://localhost:<port>"); solo se debería usar para probar una aplicación de ejemplo similar al código de ejemplo de descarga.

A continuación ValuesController se proporcionan los puntos de conexión para las pruebas:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

Pruebe el código de ejemplo anterior mediante uno de los métodos siguientes:

  • Ejecute el ejemplo con dotnet run utilizando la URL predeterminada de https://localhost:5001.
  • Ejecute el ejemplo desde Visual Studio con el puerto establecido en 44398 para una URL de https://localhost:44398.

Uso de un explorador con las herramientas F12:

  • Seleccione el botón Valores y revise los encabezados en la pestaña Red.

  • Seleccione el botón probar PUT. Consulte Mostrar solicitudes de OPCIONES para obtener instrucciones sobre cómo mostrar la solicitud de OPCIONES. La prueba PUT crea dos solicitudes, una solicitud preparatoria de OPCIONES y la solicitud PUT.

  • Seleccione el GetValues2 [DisableCors] botón para desencadenar una solicitud CORS con error. Como se mencionó en el documento, la respuesta devuelve 200 correcto, pero no se realiza la solicitud CORS. Seleccione la pestaña Consola para ver el error de CORS. Dependiendo del navegador, se muestra un error similar al siguiente:

    El acceso a la obtención en 'https://cors1.azurewebsites.net/api/values/GetValues2' desde el origen 'https://cors3.azurewebsites.net' ha sido bloqueado por la política de CORS: no hay encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Los puntos finales habilitados para CORS se pueden probar con una herramienta, como curl o Fiddler. Cuando se utiliza una herramienta, el origen de la solicitud especificada por el encabezado Origin debe diferir del host que recibe la solicitud. Si la solicitud no es de origen cruzado según el valor del encabezado Origin:

  • No es necesario que el middleware de CORS procese la solicitud.
  • Los encabezados CORS no se devuelven en la respuesta.

El siguiente comando usa curl para emitir una solicitud OPTIONS con información:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Prueba de CORS con enrutamiento de puntos de conexión y [HttpOptions]

Habilitar CORS por punto final usando RequireCors actualmente no admite solicitudes preparatorias automáticas . Considere el siguiente código que utiliza enrutamiento de punto final para habilitar CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

A continuación TodoItems1Controller se proporcionan los puntos de conexión para las pruebas:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Prueba el código anterior de la página de prueba (https://cors1.azurewebsites.net/test?number=1) de la muestra implementada.

Los botones Delete [EnableCors] y GET [EnableCors] tienen éxito, porque los puntos finales tienen [EnableCors] y responden a las solicitudes preparatorias. Se produce un error en los demás puntos de conexión. El botón GET falla, porque el JavaScript envía:

 headers: {
      "Content-Type": "x-custom-header"
 },

A continuación TodoItems2Controller se proporcionan puntos de conexión similares, pero se incluye código explícito para responder a las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

El código anterior se puede probar al implementar la muestra en Azure. En la lista desplegable Controlador, selecciona Preparatorio y después Configurar controlador. Todas las llamadas CORS a los puntos finales TodoItems2Controller tienen éxito.

Recursos adicionales

Por Rick Anderson y Kirk Larkin

En este artículo se muestra cómo habilitar CORS en una aplicación de ASP.NET Core.

La seguridad del explorador evita que una página web realice solicitudes a un dominio diferente del que atendió a dicha página web. Esta restricción se denomina directiva de mismo origen. La directiva de mismo origen evita que un sitio malintencionado lea información confidencial de otro sitio. A veces, es posible que quiera permitir que otros sitios realicen solicitudes entre orígenes a la aplicación. Para obtener más información, consulte el artículo CORS de Mozilla.

Intercambio de recursos de origen cruzado (CORS):

  • Es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
  • No es una característica de seguridad, CORS relaja la seguridad. Una API no es más segura al permitir CORS. Para más información, vea Funcionamiento de CORS.
  • Permite explícitamente a un servidor a permitir algunas solicitudes de origen cruzado y rechazar otras.
  • Es más seguro y flexible que las técnicas anteriores, como JSONP.

Vea o descargue el código de ejemplo (cómo descargarlo)

Mismo origen

Dos URL tienen el mismo origen si tienen esquemas, hosts y puertos idénticos (RFC 6454).

Estas dos direcciones URL tienen el mismo origen:

  • https://example.com/foo.html
  • https://example.com/bar.html

Estas direcciones URL tienen orígenes diferentes a las dos direcciones URL anteriores:

  • https://example.net: Dominio diferente
  • https://www.example.com/foo.html: Subdominio diferente
  • http://example.com/foo.html: Esquema diferente
  • https://example.com:9000/foo.html: Puerto diferente

Habilitación de CORS

Existen tres formas de habilitar CORS:

El uso del atributo [EnableCors] con una directiva con nombre proporciona el mejor control para limitar los puntos finales que admiten CORS.

Advertencia

UseCors debe llamarse en el orden correcto. Para obtener más información vea Orden de Middleware. Por ejemplo, UseCors debe llamarse antes de UseResponseCaching cuando se utiliza UseResponseCaching.

Cada ennfoque se detalla en las secciones siguientes.

CORS con directivas con nombre y middleware

El middleware de CORS controla las solicitudes entre orígenes. El código siguiente aplica una directiva de CORS a todos los puntos de conexión de la aplicación con los orígenes especificados:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

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

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors(MyAllowSpecificOrigins);

        // app.UseResponseCaching();

        app.UseAuthorization();

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

El código anterior:

Con el enrutamiento de puntos de conexión, el middleware de CORS debe configurarse para ejecutarse entre las llamadas a UseRouting y UseEndpoints.

Consulte Prueba CORS para obtener instrucciones sobre el código de prueba similar al código anterior.

La AddCors llamada al método añade servicios CORS al contenedor de servicios de la aplicación:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

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

Para obtener más información, consulte Opciones de directiva CORS en este documento.

Los CorsPolicyBuilder métodos se pueden encadenar, como se muestra en el código siguiente:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
    });

    services.AddControllers();
}

Nota: La URL especificada no debe contener una barra diagonal (/). Si la dirección URL finaliza con /, la comparación devuelve false y no se devuelve ningún encabezado.

CORS con directivas predeterminadas y middleware

El código resaltado siguiente habilita la directiva CORS predeterminada:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });
        });

        services.AddControllers();
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

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

El código anterior aplica la directiva CORS predeterminada a todos los puntos de conexión del controlador.

Habilitación de CORS con enrutamiento de punto de conexión

La habilitación de CORS por punto de conexión usando RequireCors no admite solicitudes preparatorias automáticas. Para obtener más información, consulte este problema de GitHub y Pruebe CORS con enrutamiento de puntos de conexión y [HttpOptions].

Con el enrutamiento de punto final, CORS se puede habilitar por punto final utilizando el RequireCors conjunto de métodos de extensión:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/echo",
                context => context.Response.WriteAsync("echo"))
                .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapControllers()
                     .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapGet("/echo2",
                context => context.Response.WriteAsync("echo2"));

            endpoints.MapRazorPages();
        });
    }
}

En el código anterior:

  • app.UseCors agrega el middleware CORS. Debido a que no se ha configurado una política predeterminada, app.UseCors() por sí solo no habilita CORS.
  • Los /echo y puntos de conexión del controlador permiten solicitudes entre orígenes mediante la directiva especificada.
  • Los /echo2 y Razor puntos de conexión de páginas no permiten solicitudes de origen cruzado porque no se especificó ninguna política predeterminada.

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final con RequireCors.

Consulte Prueba CORS con enrutamiento de punto final y [HttpOptions] para obtener instrucciones sobre el código de prueba similar al anterior.

Habilitación de CORS con atributos

Habilitar CORS con el atributo [EnableCors] y aplicar una directiva con nombre solo a los puntos de conexión que requieren CORS proporcionan el mejor control.

El atributo [EnableCors] proporciona una alternativa a aplicar CORS globalmente. El [EnableCors] atributo habilita CORS para los puntos de conexión seleccionados, en lugar de todos los puntos de conexión:

  • [EnableCors] especifica la política predeterminada.
  • [EnableCors("{Policy String}")] especifica una directiva guardada.

El atributo [EnableCors] se puede aplicar a:

  • Razor Página PageModel
  • Controlador
  • Método de acción del controlador

Se pueden aplicar diferentes directivas a controladores, modelos de página o métodos de acción con el atributo [EnableCors]. Cuando el atributo [EnableCors] se aplica a un controlador, modelo de página o método de acción, y CORS está habilitado en middleware, se aplican ambas directivas. Se recomienda combinar directivas. Use el el atributo o middleware [EnableCors], no ambos, en la misma aplicación.

El código siguiente aplica una directiva diferente a cada método:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

El siguiente código crea dos políticas CORS:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("Policy1",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                policy =>
                {
                    policy.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });
        });

        services.AddControllers();
    }

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

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

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

Para obtener el mejor control de la limitación de solicitudes CORS:

El código de la sección siguiente cumple la lista anterior.

Consulte Prueba CORS para obtener instrucciones sobre el código de prueba similar al código anterior.

Deshabilitación de CORS

El atributo [DisableCors]no deshabilita el CORS que ha sido habilitado por el enrutamiento de punto final .

El siguiente código define la política CORS "MyPolicy":

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

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

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

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

El código siguiente deshabilita CORS para la GetValues2 acción:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El código anterior:

Consulte Probar CORS para obtener instrucciones sobre cómo probar el código anterior.

Opciones de directiva CORS

En esta sección se describen las distintas opciones que se pueden establecer en una directiva de CORS:

AddPolicy es llamado por Startup.ConfigureServices. Para algunas opciones, puede resultar útil leer primero la sección Funcionamiento de CORS .

Establecimiento de los orígenes permitidos

AllowAnyOrigin: Permite solicitudes CORS desde todos los orígenes con cualquier esquema (http o https). AllowAnyOrigin no es seguro porque cualquier sitio web puede realizar solicitudes entre orígenes a la aplicación.

Nota

Especificar AllowAnyOrigin y AllowCredentials es una configuración no segura y puede dar lugar a una falsificación de solicitud entre sitios. El servicio CORS devuelve una respuesta CORS no válida cuando una aplicación está configurada con ambos métodos.

AllowAnyOrigin afecta a las solicitudes preparatorias y al Access-Control-Allow-Origin encabezado. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

SetIsOriginAllowedToAllowWildcardSubdomains: establece la IsOriginAllowed propiedad de la directiva en una función que permite que los orígenes coincidan con un dominio comodín configurado al evaluar si se permite el origen.

options.AddPolicy("MyAllowSubdomainPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

Establecimiento de los métodos HTTP permitidos

AllowAnyMethod:

  • Permite cualquier método HTTP:
  • Afecta a las solicitudes preparatorias y al encabezado Access-Control-Allow-Methods. Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Establecimiento de los encabezados de solicitud permitidos

Para permitir que se envíen encabezados específicos en una solicitud CORS, llamada encabezados de solicitud de autor , llame a WithHeaders y especifique los encabezados permitidos:

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

AllowAnyHeader afecta a las solicitudes de preventa y al encabezado Access-Control-Request-Headers . Para obtener más información, consulte la sección Solicitudes de prelanzamiento .

Una coincidencia de política de CORS Middleware con encabezados específicos especificados por WithHeaders solo es posible cuando los encabezados enviados en Access-Control-Request-Headers coinciden exactamente con los encabezados indicados en WithHeaders.

Por ejemplo, considere la posibilidad de configurar una aplicación como se indica a continuación:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

CORS Middleware rechaza la solicitud preparatoria con el siguiente encabezado de solicitud porque Content-Language (HeaderNames.ContentLanguage ) no aparece en WithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

La aplicación devuelve una respuesta 200 OK pero no devuelve los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes.

Establecer los encabezados de respuesta expuestos

De forma predeterminada, el explorador no expone todos los encabezados de respuesta a la aplicación. Para obtener más información, consulte W3C Cross-Origin Resource Sharing (Terminology): Simple Response Header.

Los encabezados de respuesta que están disponibles de forma predeterminada son:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

La especificación CORS llama a estos encabezados de respuesta simples. Para que otros encabezados estén disponibles para la aplicación, llame a WithExposedHeaders:

options.AddPolicy("MyExposeResponseHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .WithExposedHeaders("x-custom-header");
    });

Credenciales en solicitudes entre orígenes

Las credenciales requieren un control especial en una solicitud CORS. De forma predeterminada, el navegador no envía credenciales con una solicitud de origen cruzado. Las credenciales incluyen cookies y esquemas de autenticación HTTP. Para enviar credenciales con una solicitud de origen cruzado, el cliente debe establecer XMLHttpRequest.withCredentials en true.

Uso XMLHttpRequest directo:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Uso de jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Uso de la Fetch API:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

El servidor debe permitir las credenciales. Para permitir credenciales de origen cruzado, llame a AllowCredentials:

options.AddPolicy("MyMyAllowCredentialsPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .AllowCredentials();
    });

La respuesta HTTP incluye un encabezado Access-Control-Allow-Credentials, que le dice al navegador que el servidor permite credenciales para una solicitud de origen cruzado.

Si el explorador envía credenciales, pero la respuesta no incluye un encabezado válido Access-Control-Allow-Credentials, el explorador no expone la respuesta a la aplicación y se produce un error en la solicitud entre orígenes.

Permitir credenciales entre orígenes es un riesgo de seguridad. Un sitio web de otro dominio puede enviar las credenciales de un usuario que ha iniciado sesión a la aplicación en nombre del usuario sin el conocimiento del usuario.

La especificación CORS también indica que establecer orígenes en "*" (todos los orígenes) no es válido si el Access-Control-Allow-Credentials encabezado está presente.

Solicitud preparatoria

Para algunas solicitudes CORS, el navegador envía una solicitud adicional de OPTIONS antes de realizar la solicitud real. Esta solicitud se denomina solicitud preparatoria. El navegador puede omitir la solicitud preparatoria si se cumplen todas las siguientes condiciones:

  • El método de solicitud es GET, HE0AD o POST.
  • La aplicación no establece encabezados de solicitud que no sean Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID.
  • El encabezado Content-Type, si está configurado, tiene uno de los siguientes valores:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

La regla sobre encabezados de solicitud establecida para la solicitud del cliente se aplica a los encabezados que la aplicación establece llamando a setRequestHeader en el objeto XMLHttpRequest. La especificación CORS llama a estos encabezados de solicitud de autor. La regla no se aplica a los encabezados que el explorador puede establecer, como User-Agent, Hosto Content-Length.

La siguiente es una respuesta de ejemplo similar a la solicitud preparatoria realizada desde el botón [Put test] en la sección Test CORS de este documento.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

La solicitud preparatoria utiliza el método OPCIONES HTTP . Puede incluir los siguientes encabezados:

Si se rechaza la solicitud de preventa, la aplicación devuelve una respuesta 200 OK pero no establece los encabezados CORS. Por lo tanto, el explorador no intenta la solicitud entre orígenes. Para obtener un ejemplo de una solicitud preparatoria denegada, consulte la sección Probar CORS de este documento.

Con las herramientas F12, la aplicación de consola muestra un error similar a uno de los siguientes, según el explorador:

  • Firefox: solicitud de origen cruzado bloqueada: la política del mismo origen no permite leer el recurso remoto en https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Motivo: la solicitud CORS no se realizó correctamente). Más información
  • Chromium basado: la directiva de CORS ha bloqueado el acceso para capturar en "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" desde el origenhttps://cors3.azurewebsites.net: La respuesta a la solicitud preparatoria no pasa la comprobación de control de acceso: no hay ningún encabezado "Access-Control-Allow-Origin" presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Para permitir encabezados específicos, llame a WithHeaders:

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Para permitir todos los encabezados de solicitud de autor, llame a AllowAnyHeader:

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

Los navegadores no son consistentes en la forma en que configuran Access-Control-Request-Headers. Si alguna de las dos opciones:

  • Los encabezados se establecen en cualquier otro valor que no sea "*"
  • AllowAnyHeader se llama a : incluya al menos Accept, Content-Typey Origin, además de los encabezados personalizados que quiera admitir.

Código de solicitud de preparatoria automático

Cuando se aplica la directiva CORS:

  • Globalmente llamando a app.UseCors en Startup.Configure.
  • Uso del atributo [EnableCors].

ASP.NET Core responde a la solicitud preparatoria de OPTIONS.

Habilitar CORS por punto final usando RequireCors actualmente no admite solicitudes automáticas preparatorias.

La sección CORS de prueba de este documento muestra este comportamiento.

Atributo [HttpOptions] para solicitudes preparatorias

Cuando CORS está habilitado con la directiva adecuada, ASP.NET Core suele responder automáticamente a las solicitudes previas de CORS. En algunos escenarios, es posible que esto no sea el caso. Por ejemplo, usando CORS con enrutamiento de punto final .

El código siguiente usa el atributo [HttpOptions] para crear puntos de conexión para las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Consulte Prueba CORS con enrutamiento de punto final y [HttpOptions] para obtener instrucciones sobre cómo probar el código anterior.

Establecer la hora de expiración previa

El encabezado Access-Control-Max-Age especifica cuánto tiempo se puede almacenar en caché la respuesta a la solicitud preparatoria. Para establecer este encabezado, llame a SetPreflightMaxAge:

options.AddPolicy("MySetPreflightExpirationPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

Cómo funciona la CORS

Esta sección describe lo que sucede en una solicitud CORS a nivel de los mensajes HTTP.

  • CORS no es una función de seguridad. El uso compartido de recursos entre orígenes (CORS) es una norma de W3C que permite mayor flexibilidad a los servidores en la directiva de mismo origen.
    • Por ejemplo, un actor malicioso podría usar Cross-Site Scripting (XSS) contra su sitio y ejecutar una solicitud entre sitios a su sitio habilitado para CORS para robar información.
  • Una API no es más segura al permitir CORS.
    • Es necesario que el cliente (explorador) aplique CORS. El servidor ejecuta la solicitud y devuelve la respuesta, es el cliente que devuelve un error y bloquea la respuesta. Por ejemplo, cualquiera de las siguientes herramientas mostrará la respuesta del servidor:
  • Es una forma de que un servidor permita a los navegadores ejecutar una solicitud XHR o Fetch API de origen cruzado que, de lo contrario, estaría prohibida.
    • Los navegadores sin CORS no pueden hacer solicitudes de origen cruzado. Antes de CORS, se usaba JSONP para eludir esta restricción. JSONP no utiliza XHR, utiliza la etiqueta <script> para recibir la respuesta. Se permite cargar scripts entre orígenes.

La especificación CORS introdujo varios encabezados HTTP nuevos que permiten solicitudes de origen cruzado. Si un explorador admite CORS, establece estos encabezados automáticamente para las solicitudes entre orígenes. El código JavaScript personalizado no es necesario para habilitar CORS.

El siguiente es un ejemplo de una solicitud de origen cruzado desde el botón de prueba Valores a https://cors1.azurewebsites.net/api/values. El encabezado Origin:

  • Proporciona el dominio del sitio que realiza la solicitud.
  • Es necesario y debe ser diferente del host.

Encabezados generales

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Encabezados de respuesta

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

En las solicitudes OPTIONS, el servidor establece el encabezado Encabezados de respuesta Access-Control-Allow-Origin: {allowed origin} en la respuesta. Por ejemplo, la solicitud del botónejemplo, Eliminar [EnableCors]OPTIONS desplegada contiene los siguientes encabezados:

Encabezados generales

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Encabezados de respuesta

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Encabezados de solicitud

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

En los encabezados de respuesta anteriores, el servidor establece el encabezado Access-Control-Allow-Origin en la respuesta. El valor https://cors1.azurewebsites.net de este encabezado coincide con el encabezado Origin de la solicitud.

Si se llama aAllowAnyOrigin, el Access-Control-Allow-Origin: * se devuelve el valor comodín . AllowAnyOrigin permite cualquier origen.

Si la respuesta no incluye el encabezado Access-Control-Allow-Origin, la solicitud de origen cruzado falla. En concreto, el navegador no permite la solicitud. Incluso si el servidor devuelve una respuesta correcta, el explorador no hace que la respuesta esté disponible para la aplicación cliente.

Mostrar solicitudes OPTIONS

De forma predeterminada, los exploradores Chrome y Edge no muestran las solicitudes OPTIONS en la pestaña de red de las herramientas F12. Para mostrar las solicitudes OPTIONS en estos exploradores:

  • chrome://flags/#out-of-blink-cors o edge://flags/#out-of-blink-cors
  • deshabilite la marca.
  • Reiniciar.

Firefox muestra las solicitudes OPTIONS de forma predeterminada.

CORS en IIS

Al implementar en IIS, CORS debe ejecutarse antes de la autenticación de Windows si el servidor no está configurado para permitir el acceso anónimo. Para admitir este escenario, el módulo CORS de IIS debe instalarse y configurarse para la aplicación.

Prueba de CORS

La descarga de muestra tiene código para probar CORS. Vea cómo descargarlo. El ejemplo es un proyecto de API con Razor páginas agregadas:

public class StartupTest2
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

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

Advertencia

WithOrigins("https://localhost:<port>"); solo se debería usar para probar una aplicación de ejemplo similar al código de ejemplo de descarga.

A continuación ValuesController se proporcionan los puntos de conexión para las pruebas:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

El paquete de NuGet Rick.Docs.Samples.RouteInfo proporciona MyDisplayRouteInfo y se muestra la información de ruta.

Pruebe el código de ejemplo anterior mediante uno de los métodos siguientes:

  • Ejecute el ejemplo con dotnet run utilizando la URL predeterminada de https://localhost:5001.
  • Ejecute el ejemplo desde Visual Studio con el puerto establecido en 44398 para una URL de https://localhost:44398.

Uso de un explorador con las herramientas F12:

  • Seleccione el botón Valores y revise los encabezados en la pestaña Red.

  • Seleccione el botón probar PUT. Consulte Mostrar solicitudes de OPCIONES para obtener instrucciones sobre cómo mostrar la solicitud de OPCIONES. La prueba PUT crea dos solicitudes, una solicitud preparatoria de OPCIONES y la solicitud PUT.

  • Seleccione el GetValues2 [DisableCors] botón para desencadenar una solicitud CORS con error. Como se mencionó en el documento, la respuesta devuelve 200 correcto, pero no se realiza la solicitud CORS. Seleccione la pestaña Consola para ver el error de CORS. Dependiendo del navegador, se muestra un error similar al siguiente:

    El acceso a la obtención en 'https://cors1.azurewebsites.net/api/values/GetValues2' desde el origen 'https://cors3.azurewebsites.net' ha sido bloqueado por la política de CORS: no hay encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado. Si una respuesta opaca sirve a sus necesidades, establezca el modo de la solicitud en 'no-cors' para obtener el recurso con CORS deshabilitado.

Los puntos finales habilitados para CORS se pueden probar con una herramienta, como curl o Fiddler. Cuando se utiliza una herramienta, el origen de la solicitud especificada por el encabezado Origin debe diferir del host que recibe la solicitud. Si la solicitud no es de origen cruzado según el valor del encabezado Origin:

  • No es necesario que el middleware de CORS procese la solicitud.
  • Los encabezados CORS no se devuelven en la respuesta.

El siguiente comando usa curl para emitir una solicitud OPTIONS con información:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Prueba de CORS con enrutamiento de puntos de conexión y [HttpOptions]

Habilitar CORS por punto final usando RequireCors actualmente no admite solicitudes preparatorias automáticas . Considere el siguiente código que utiliza enrutamiento de punto final para habilitar CORS:

public class StartupEndPointBugTest
{
    readonly string MyPolicy = "_myPolicy";

    // .WithHeaders(HeaderNames.ContentType, "x-custom-header")
    // forces browsers to require a preflight request with GET

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyPolicy,
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com",
                                        "https://cors1.azurewebsites.net",
                                        "https://cors3.azurewebsites.net",
                                        "https://localhost:44398",
                                        "https://localhost:5001")
                           .WithHeaders(HeaderNames.ContentType, "x-custom-header")
                           .WithMethods("PUT", "DELETE", "GET", "OPTIONS");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

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

A continuación TodoItems1Controller se proporcionan los puntos de conexión para las pruebas:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Prueba el código anterior de la página de prueba (https://cors1.azurewebsites.net/test?number=1) de la muestra implementada.

Los botones Delete [EnableCors] y GET [EnableCors] tienen éxito, porque los puntos finales tienen [EnableCors] y responden a las solicitudes preparatorias. Se produce un error en los demás puntos de conexión. El botón GET falla, porque el JavaScript envía:

 headers: {
      "Content-Type": "x-custom-header"
 },

A continuación TodoItems2Controller se proporcionan puntos de conexión similares, pero se incluye código explícito para responder a las solicitudes OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

El código anterior se puede probar al implementar la muestra en Azure. En la lista desplegable Controlador, selecciona Preparatorio y después Configurar controlador. Todas las llamadas CORS a los puntos finales TodoItems2Controller tienen éxito.

Recursos adicionales