Share via


ASP.NET Web API

Funciones para CORS en ASP.NET Web API 2

Brock Allen

El uso compartido de recursos con origen cruzado (Cross-Origin Resource Sharing, CORS) es una especificación del World Wide Web Consortium (W3C) que frecuentemente se considera como parte de HTML5 y que permite sobreponerse en JavaScript a la restricción de seguridad de la directiva de un mismo origen impuesta por los exploradores. La directiva de un mismo origen significa que el código JavaScript solo puede realizar llamadas AJAX al mismo origen de la página web contenedora (donde “origen” se define como el conjunto de nombre de host, protocolo y puerto). Por ejemplo, el código JavaScript en una página web de http://foo.com no puede realizar llamadas AJAX a http://bar.com (de hecho, tampoco a http://www.foo.com, https://foo.com ni a http://foo.com:999).

CORS relaja esta restricción al permitir que los servidores especifiquen qué orígenes tienen permiso para llamarlos. El cumplimiento de CORS se lleva a cabo en el explorador, pero la implementación se debe realizar en el servidor, y la versión más reciente de ASP.NET Web API 2 es completamente compatible con CORS. Con Web API 2, podemos configurar directivas para permitir que los clientes JavaScript de diferentes orígenes accedan a nuestras API.

Introducción a CORS

Para usar las características nuevas de CORS en Web API, conviene entender los detalles de CORS mismo, ya que la implementación en Web API es fiel a la especificación. Estos detalles pueden parecer puntillosos en este momento, pero resultarán útiles más adelante para entender las configuraciones disponibles en Web API; y cuando depuremos CORS nos permitirán resolver los problemas en forma más rápida.

El mecanismo general de CORS funciona de modo tal que cuando JavaScript trata de realizar una llamada AJAX de origen cruzado, el explorador le “pregunta” al servidor si esto está permitido, para lo cual envía encabezados en la solicitud HTTP (por ejemplo Origin). Para indicar qué está permitido, el servidor devuelve encabezados HTTP en la respuesta (por ejemplo, Access-Control-Allow-Origin). Esta revisión de los permisos se realiza para cada URL distinta que el cliente invoca, lo que significa que diferentes direcciones URL pueden tener permisos diferentes.

Además del origen, CORS permite que un servidor indique qué métodos HTTP están permitidos, qué encabezados HTTP de solicitud puede enviar el cliente, qué encabezados HTTP de respuesta puede leer el cliente y si el explorador tiene permiso para enviar o recibir credenciales (cookies o encabezados de autenticación) en forma automática. Otros encabezados de solicitud y respuesta adicionales indican cuáles de estas características están permitidas. Estos encabezados se resumen en la figura 1 (observe que algunas de las características no envían ningún encabezado en la solicitud, solo en la respuesta).

Figura 1 Encabezados HTTP de CORS

Permiso/característica Encabezado de solicitud Encabezado de respuesta
Origen Origin Access-Control-Allow-Origin
Método HTTP Access-Control-Request-Method Access-Control-Allow-Method
Encabezados de solicitud Access-Control-Request-Headers Access-Control-Allow-Headers
Encabezados de respuesta   Access-Control-Expose-Headers
Credenciales   Access-Control-Allow-Credentials
Respuesta del almacenamiento caché de preflight   Access-Control-Max-Age

Los exploradores tienen dos maneras diferentes para pedirle estos permisos al servidor: solicitudes CORS simples y solicitudes CORS de preflight.

Solicitudes CORS simples Este es un ejemplo de una solicitud CORS simple:

POST http://localhost/WebApiCorsServer/Resources/ HTTP/1.1
Host: localhost
Accept: */*
Origin: http://localhost:55912
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
value1=foo&value2=5

Y la respuesta:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}

La solicitud es una solicitud de origen cruzado de http://localhost:55912 a http://localhost, y el explorador agrega un encabezado HTTP Origin a la solicitud para indicar el origen de la llamada al servidor. El servidor responde con un encabezado de respuesta Access-­Control-Allow-Origin para indicar que este origen está permitido. El explorador hace valer la directiva del servidor y JavaScript recibirá la devolución de llamada normal de una transacción correcta.

El servidor puede responder ya sea con el valor del origen exacto de la solicitud o con el valor “*” para indicar que se permite cualquier origen. Si el servidor no hubiera permitido el origen de la llamada, entonces el encabezado Access-Control-Allow-Origin simplemente estaría ausente y se invocaría la devolución de llamada de error de JavaScript.

Observe que en el caso de una solicitud CORS simple, la llamada en el servidor también se invoca. Esto puede sorprenderlo si todavía está aprendiendo CORS, pero este comportamiento no es diferente de la situación donde el explorador construye un elemento <form> y realiza una solicitud POST normal. CORS no previene que la llamada se invoque en el servidor; más bien, previene que el código JavaScript que realiza la llamada reciba los resultados. Si deseamos prevenir que el autor de la llamada invoque el servidor, entonces debemos implementar algún tipo de autorización en el código del servidor (posiblemente con el atributo de filtros de autorización [Authorize]).

El ejemplo anterior se conoce como una solicitud CORS simple, porque el tipo de llamada AJAX desde el cliente era GET o POST; porque el Content-Type era application/x-www-form-­urlencoded, multipart/form-data o text/plain; y porque no se enviaron encabezados de solicitud adicionales. Si la llamada AJAX fuera otro método HTTP, el Content-Type tuviera un valor diferente o si el cliente quisiera enviar encabezados de solicitud adicionales, entonces la solicitud se consideraría como una solicitud de preflight. La mecánica de las solicitudes de preflight son ligeramente diferentes.

Solicitudes CORS de preflight Si una llamada AJAX no es una solicitud simple, entonces requiere de una solicitud CORS de preflight, que es simplemente una solicitud HTTP adicional al servidor para obtener permiso. Esta solicitud de preflight la realiza el explorador en forma automática y usa el método HTTP OPTIONS. Si el servidor responde correctamente a la solicitud de preflight y otorga el permiso, entonces el explorador realizará la llamada AJAX propiamente tal que JavaScript está tratando de realizar.

Si nos preocupa el rendimiento (¿y cuándo no?), entonces el resultado de esta solicitud de preflight se puede almacenar en caché en el explorador al incluir el encabezado Access-Control-Max-Age en la respuesta de preflight. El valor contiene el tiempo en segundos durante el cual los permisos se pueden almacenar en caché.

Este es un ejemplo de una solicitud CORS de preflight:

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: PUT
Origin: http://localhost:55912
Access-Control-Request-Headers: content-type
Accept: */*

Y la respuesta de preflight:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: content-typeAccess-Control-Max-Age: 600

Esta es la solicitud AJAX propiamente tal:

PUT http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Content-Length: 27
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:55912
Content-Type: application/json
{"value1":"foo","value2":5}

Y la respuesta AJAX:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}

Observe que en este ejemplo se desencadena una solicitud CORS de preflight debido a que el método HTTP es PUT y el cliente tiene que enviar el encabezado Content-Type para indicar que la solicitud contiene application/json. En la solicitud de preflight (además de Origin), se usan los encabezados de solicitud Access-Control-Request-Method y Access-Control-Request-Headers para pedir permiso para el tipo de método HTTP y el encabezado adicional que el cliente desea enviar.

El servidor otorgó el permiso (y estableció la duración del almacenamiento caché de preflight) y luego el explorador permitió la llamada AJAX propiamente tal. Si el servidor no hubiera dado permiso a ninguna de las funciones solicitadas, entonces el encabezado de respuesta correspondiente habría estado ausente, la llamada AJAX no se habría realizado y se habría invocado la devolución de llamada de error de JavaScript en su lugar.

Las solicitudes y respuestas HTTP anteriores se realizaron con Firefox. En el caso de usar Internet Explorer, veríamos que se solicita un encabezado Accept adicional. En el caso de Chrome, además se solicitaría Accept y Origin. Curiosamente, ni Accept ni Origin aparecen en Access-Control-­Allow-Headers, ya que la especificación dice que son implícitas y que se pueden omitir (que es lo que hace Web API). Se puede debatir si realmente hay que solicitar Origin y Accept, pero dada la manera en que funcionan estos exploradores hoy en día, nuestra directiva Web API CORS muy probablemente deberá incluirlas. Me parece lamentable que los proveedores de los exploradores no se pongan de acuerdo sobre la interpretación de la especificación.

Encabezados de respuesta Resulta fácil darle permiso a un cliente para que acceda a los encabezados de respuesta mediante el encabezado de respuesta Access-Control-Expose-Headers. Este es un ejemplo de una respuesta HTTP que permite que el código JavaScript que da origen a la llamada acceda al encabezado de respuesta personalizado “bar”:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912Access-Control-Expose-Headers: bar
bar: a bar value
Content-Length: 27
{"Value1":"foo","Value2":5}

El cliente JavaScript puede usar simplemente la función XMLHttpRequest getResponseHeader para leer el valor. Este es un ejemplo con jQuery:

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  // other settings omitted
}).done(function (data, status, xhr) {
  var bar = xhr.getResponseHeader("bar");
  alert(bar);
});

Credenciales y autenticación Posiblemente el aspecto que más confusión causa de CORS son las credenciales y la autenticación. Por lo general, la autenticación con las Web API se puede realizar ya sea con una cookie o con un encabezado Authorization (existen otras formas, pero estas son las más comunes). En la actividad normal del explorador, si uno de estos métodos se estableció previamente, entonces el explorador pasa estos valores en forma implícita al servidor en las solicitudes subsiguientes. En el caso de AJAX de origen cruzado, sin embargo, este traspaso implícito de los valores se debe solicitar en forma explícita en JavaScript (mediante la marca withCredentials en XMLHttpRequest) y se debe permitir en forma explícita en la directiva CORS del servidor (mediante el encabezado de respuesta Access-Control-Allow-Credentials).

Este es un ejemplo de un cliente JavaScript que establece la marca withCredentials con jQuery:

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  xhrFields: {
    withCredentials: true
  }
  // Other settings omitted
});

La marca withCredentials hace dos cosas: Si el servidor emite una cookie, el explorador puede aceptarla; si el explorador tiene una cookie, puede enviarla al servidor.

Este es un ejemplo de la respuesta HTTP para permitir las credenciales:

HTTP/1.1 200 OK
Set-Cookie: foo=1379020091825
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Credentials: true

El encabezado de respuesta Access-Control-Allow-Credentials hace dos cosas: si la respuesta tiene una cookie, el explorador puede aceptarla; y si el explorador envió una cookie con la solicitud, el cliente JavaScript puede recibir los resultados de la llamada. En otras palabras, si el cliente establece withCredentials, entonces el cliente solo recibirá una devolución de llamada con el acierto en JavaScript si el servidor permite credenciales (en la respuesta). Si se hubiera establecido withCredentials y el servidor no permitiera las credenciales, entonces el cliente no obtendría acceso a los resultados y se invocaría la devolución de llamada de error de cliente.

El mismo conjunto de reglas y comportamientos se aplica si se usa el encabezado Authorization en vez de las cookies (por ejemplo, al usar la autenticación del tipo Basic o Integrated Windows). Una nota interesante sobre el uso de credenciales en el encabezado Authorization: no hace falta que el servidor otorgue en forma explícita el encabezado Authorization en el encabezado de respuesta CORS Access-Control-Allow-Headers.

Observe que con el encabezado CORS de respuesta Access-Control-Allow-Credentials, si el servidor emite este encabezado, entonces el valor comodín “*” no se puede usar para Access-Control-Allow-Origin. En vez de esto, la especificación de CORS exige que se use el origen explícito. El marco de Web API se encarga de todo esto por nosotros, pero lo menciono aquí ya que podría toparse con este comportamiento durante la depuración.

Este análisis de las credenciales y la autenticación tiene un giro interesante. La descripción hasta el momento se ha centrado en la situación donde el explorador envía credenciales en forma implícita. Es posible que un cliente JavaScript envíe las credenciales en forma explícita (nuevamente, a través del encabezado Authorization). Si este es el caso, entonces no se cumple ninguna de las reglas y comportamientos que acabamos de ver.

En esta situación, el cliente establecería en forma explícita el encabezado Authorization en la solicitud y no haría falta que establezca withCredentials en XMLHttpRequest. Este encabezado desencadenaría una solicitud de preflight y el servidor tendría que permitir el encabezado Authorization con el encabezado CORS de respuesta Access-Control-Allow-Headers. Además, el servidor no tendría que emitir el encabezado CORS de respuesta Access-Control-­Allow-Credentials.

Así es como se vería el código del cliente para establecer el encabezado Authorization en forma explícita:

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  headers: {
    "Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3Mi..."
  }
  // Other settings omitted
});

Esta es la solicitud de preflight:

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: GET
Origin: http://localhost:55912
Access-Control-Request-Headers: authorization
Accept: */*

Esta es la respuesta de preflight:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: authorization

Establecer el valor de un token en forma explícita en el encabezado Authorization es una forma de autenticación más segura, ya que evitamos la posibilidad de ataques de falsificación de solicitud entre sitios (CSRF). Podemos ver esta estrategia en las nuevas plantillas de Aplicación de una sola página (SPA) de Visual Studio 2013.

Ahora que ya conoce los aspectos básicos de CORS en el nivel de HTTP, le mostraré cómo usar el nuevo marco de CORS para emitir estos encabezados desde Web API.

CORS en Web API 2

Las funciones CORS de Web API son un marco completo para permitir que una aplicación defina los permisos para las solicitudes CORS. El marco gira en torno al concepto de una directiva que permite especificar las características de CORS que se permiten para cualquier solicitud dada que se envíe a la aplicación.

Primero, para obtener el marco CORS, debe hacer referencia a las bibliotecas de CORS desde la aplicación Web API (en Visual Studio 2013 no hay referencias predeterminadas en ninguna de las plantillas de Web API). El marco CORS de Web API está disponible a través de NuGet con el paquete Microsoft.AspNet.WebApi.Cors. Si no usa NuGet, también está disponible como parte de Visual Studio 2013; en este caso deberá establecer referencias a dos ensamblados: System.Web.Http.Cors.dll y System.Web.Cors.dll (en mi máquina estos se encuentran en C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages).

Luego, para expresar la directiva, Web API ofrece una clase de atributos personalizada llamada EnableCorsAttribute. Esta clase contiene propiedades para los orígenes permitidos, métodos HTTP, encabezados de solicitud y de respuesta, y si se permiten o no las credenciales (estas modelan todos los detalles de la especificación CORS que vimos previamente).

Por último, para que el marco CORS de Web API procese las solicitudes CORS y emita los encabezados CORS de respuesta apropiados, debe examinar cada solicitud que se realiza a la aplicación. Web API tiene un punto de extensión para este tipo de intercepción, por medio de controladores de mensajes. En forma apropiada, el marco CORS de Web API implementa un controlador de mensajes llamado CorsMessageHandler. En el caso de las solicitudes CORS, consultará la directiva que se expresa en el atributo del método que se invoca y emitirá los encabezados CORS de respuesta apropiados.

EnableCorsAttribute La clase EnableCorsAttribute es la forma que tiene una aplicación para expresar su directiva CORS. La clase EnableCorsAttribute tiene un constructor sobrecargado que puede aceptar tres o cuatro parámetros. Los parámetros son (en ese mismo orden):

  1. Lista de orígenes permitidos
  2. Lista de encabezados de solicitud permitidos
  3. Lista de métodos HTTP permitidos
  4. Lista de encabezados de respuesta permitidos (opcional)

También hay una propiedad para permitir credenciales (Supports­Credentials) y otra para especificar la duración del almacenamiento caché de preflight (PreflightMaxAge).

En la figura 2 se muestra un ejemplo de cómo aplicar el atributo EnableCors a un método específico en un controlador. Los valores que se usan para las diferentes configuraciones de las directivas CORS deberían coincidir con las solicitudes y respuestas CORS que vimos en los ejemplos anteriores.

Figura 2 Aplicación del atributo EnableCors a los métodos de acción

public class ResourcesController : ApiController
{
  [EnableCors("http://localhost:55912", // Origin
              null,                     // Request headers
              "GET",                    // HTTP methods
              "bar",                    // Response headers
              SupportsCredentials=true  // Allow credentials
  )]
  public HttpResponseMessage Get(int id)
  {
    var resp = Request.CreateResponse(HttpStatusCode.NoContent);
    resp.Headers.Add("bar", "a bar value");
    return resp;
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "PUT",                          // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "POST",                         // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

Observe que todos los parámetros del constructor son cadenas. Los valores múltiples se indican como listas separadas por comas (tal como se especifican los encabezados de solicitud permitidos en la figura 2). Si desea permitir todos los orígenes, encabezados de solicitud o métodos HTTP, puede usar el valor “*” (pero los encabezados de respuesta debe indicarlos en forma explícita).

Además de aplicar el atributo EnableCors en el nivel del método, también podemos aplicarlo en el nivel de la clase o en forma global para toda la aplicación. El nivel en el que se aplica el atributo configura para CORS todas las solicitudes de ese nivel más los niveles inferiores en nuestro código de Web API. Luego, si por ejemplo lo aplicamos en el nivel del método, la directiva solo se aplicará para las solicitudes de esa acción específica, mientras que si se aplica en el nivel de la clase, la directiva valdrá para todas las solicitudes que se envían a ese controlador. Finalmente, si se aplica en forma global, la directiva será para todas las solicitudes.

A continuación hay otro ejemplo de cómo aplicar el atributo en el nivel de la clase. Los valores que se usan en este ejemplo son bastante tolerantes, ya que se usa el comodín para los orígenes, encabezados de solicitud y métodos HTTP permitidos:

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

Si hay una directiva en diferentes lugares, entonces se usa el atributo más “próximo” y se hace caso omiso de los otros (es decir, el orden de precedencia es método, luego clase y luego global). Si aplicamos la directiva en un nivel superior, pero luego queremos excluir una solicitud en un nivel inferior, podemos usar otra clase de atributos, llamada DisableCorsAttribute. Este atributo es, esencialmente, una directiva que no permite nada.

Si tenemos otros métodos en el controlador donde no queremos permitir CORS, tenemos dos opciones. Primero, podemos ser explícitos en la lista de métodos HTTP, tal como se ilustra en la figura 3. O podemos dejar el comodín, pero excluir el método Delete con el atributo DisableCors, tal como se ilustra en la figura 4.

Figura 3 Uso de valores explícitos para los métodos HTTP

[EnableCors("*", "*", "PUT, POST")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because DELETE is not in the method list above
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

Figura 4 Uso del atributo DisableCors

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because of the [DisableCors] attribute
  [DisableCors]
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

CorsMessageHandler CorsMessageHandler debe estar habilitado para que el marco CORS pueda realizar la tarea de interceptar las solicitudes para evaluar la directiva CORS y emitir los encabezados CORS de respuesta. La habilitación del controlador de mensajes generalmente se realiza en la clase de configuración de Web API de la aplicación al invocar el método de extensión EnableCors:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
  }
}

Si queremos proporcionar una directiva CORS global, podemos pasar una instancia de la clase EnableCorsAttribute como parámetro al método EnableCors. Por ejemplo, el siguiente código configuraría una directiva CORS en forma global dentro de la aplicación:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
  }
}

Igual que con todos los controladores de mensajes, CorsMessageHandler puede registrarse también por ruta en vez de globalmente.

Esto sería el marco CORS básico, “directo de fábrica” en ASP.NET Web API 2. Una cosa práctica del marco es que se puede extender para situaciones más dinámicas, como veremos a continuación.

Personalización de las directivas

De los ejemplos anteriores debería desprenderse claramente que la lista de orígenes (si no se usa el comodín) es una lista estática que se compila en el código de Web API. Si bien esto puede funcionar durante el desarrollo o para algunas situaciones específicas, no es suficiente si la lista de orígenes (u otros permisos) se determina en forma dinámica (por ejemplo desde un base de datos).

Afortunadamente, el marco CORS en Web API es extensible, de modo que resulta fácil emplear una lista dinámica con orígenes. De hecho, el marco es tan flexible que hay dos métodos generales para personalizar la generación de las directivas.

Atributo de directiva CORS personalizada Una forma de permitir una directiva CORS dinámica es desarrollar una clase de atributo personalizada que pueda generar la directiva a partir de un origen de datos. Esta clase de atributo personalizada se puede usar en vez de la clase EnableCorsAttribute proporcionada por Web API. Este método es sencillo y conserva el control pormenorizado al poder aplicar un atributo en algunas clases y métodos específicos (y no aplicarlas en otros), de acuerdo con las necesidades.

Para implementar este método, simplemente creamos un atributo personalizado similar a la clase EnableCorsAttribute. El eje central es la interfaz ICorsPolicyProvider, que es la responsable de crear una instancia de una CorsPolicy para cada solicitud dada. En la figura 5 vemos un ejemplo.

Figura 5 Un atributo CORS de directiva personalizado

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
public class EnableCorsForPaidCustomersAttribute :
  Attribute, ICorsPolicyProvider
{
  public async Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    if (await IsOriginFromAPaidCustomer(originRequested))
    {
      // Grant CORS request
      var policy = new CorsPolicy
      {
        AllowAnyHeader = true,
        AllowAnyMethod = true,
      };
      policy.Origins.Add(originRequested);
      return policy;
    }
    else
    {
      // Reject CORS request
      return null;
    }
  }
  private async Task<bool> IsOriginFromAPaidCustomer(
    string originRequested)
  {
    // Do database look up here to determine if origin should be allowed
    return true;
  }
}

La clase CorsPolicy tiene todas las propiedades necesarias para expresar el permiso CORS que hay que otorgar. Los valores que se usan aquí son solo un ejemplo, pero probablemente se podrán rellenar en forma dinámica mediante una consulta a una base de datos (o de cualquier otro origen).

Fábrica de proveedor de directivas personalizadas El segundo método general para crear una directiva CORS dinámica es crear una fábrica de proveedor de directivas personalizadas. Esta es la parte del marco CORS que obtiene el proveedor de directivas para la solicitud actual. La implementación predeterminada de Web API usa los atributos personalizados para descubrir el proveedor de directivas (como vimos previamente, el proveedor de directivas era la clase de atributo misma). Esta es otra pieza acoplable del marco CORS y, si para las directivas quisiéramos usar un método diferente a los atributos personalizados, implementaríamos nuestra propia fábrica de proveedor de directivas.

El método basado en atributos que se describió previamente ofrece una asociación implícita desde una solicitud a una directiva. Un método con un proveedor de directivas personalizadas es distinto del método anterior con atributos, ya que requiere que nuestra implementación proporcione la lógica para comparar las solicitudes entrante con las directivas. Este método es más grueso, ya que es esencialmente un sistema centralizado para obtener una directiva CORS.

En la figura 6 vemos un ejemplo de cómo puede verse una fábrica de proveedor de directivas personalizadas. Este ejemplo se centra principalmente en la implementación de la interfaz ICorsPolicyProviderFactory y su método GetCorsPolicyProvider.

Figura 6 Una fábrica de proveedor de directivas personalizadas

public class DynamicPolicyProviderFactory : ICorsPolicyProviderFactory
{
  public ICorsPolicyProvider GetCorsPolicyProvider(
    HttpRequestMessage request)
  {
    var route = request.GetRouteData();
    var controller = (string)route.Values["controller"];
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    var policy = GetPolicyForControllerAndOrigin(
      controller, originRequested);
    return new CustomPolicyProvider(policy);
  }
  private CorsPolicy GetPolicyForControllerAndOrigin(
   string controller, string originRequested)
  {
    // Do database lookup to determine if the controller is allowed for
    // the origin and create CorsPolicy if it is (otherwise return null)
    var policy = new CorsPolicy();
    policy.Origins.Add(originRequested);
    policy.Methods.Add("GET");
    return policy;
  }
}
public class CustomPolicyProvider : ICorsPolicyProvider
{
  CorsPolicy policy;
  public CustomPolicyProvider(CorsPolicy policy)
  {
    this.policy = policy;
  }
  public Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    return Task.FromResult(this.policy);
  }
}

La principal diferencia en este método es que queda completamente a cargo de la implementación determinar la directiva a partir de la solicitud entrante. En la figura 6, el controlador y el origen se podrían usar para consultar los valores de la directiva en una base de datos. De nuevo, este método es el más flexible pero probablemente requiera de más trabajo para determinar la directiva a partir de la solicitud.

Para usar la fábrica de proveedor de directivas personalizadas, debemos registrarla en Web API a través del método de extensión SetCorsPolicyProviderFactory en la configuración de Web API:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
    config.SetCorsPolicyProviderFactory(
      new DynamicPolicyProviderFactory());
  }
}

Contribuciones de la comunidad en acción

ASP.NET Web API es un marco de código abierto y forma parte de un conjunto mayor de marcos de código abierto llamado ASP.NET Web Stack, que también incluyen MVC y Web Pages, entre otros.

Estos marcos se usan para crear la plataforma ASP.NET y son custodiados por el equipo de ASP.NET de Microsoft. En su papel de custodio de una plataforma de código abierto, el equipo de ASP.NET agradece las contribuciones de la comunidad, y la implementación de uso compartido de recursos con origen cruzado (CORS) en Web API es una de esas contribuciones.

Fue desarrollada originalmente por Brock Allen como parte de la biblioteca de seguridad IdentityModel de thinktecture (thinktecture.github.io).

Depuración de CORS

Se vienen en mente algunas técnicas para depurar CORS si (y cuando) las llamadas AJAX de origen cruzado no funcionan.

Del lado cliente Una forma de depurar es usar simplemente el depurador de HTTP preferido (por ejemplo Fiddler) e inspeccionar todas las solicitudes HTTP. Armados con los conocimientos que recogimos sobre los detalles de la especificación de CORS, generalmente deberíamos ser capaces de aclarar por qué una solicitud AJAX determinada no recibe permiso al inspeccionar los encabezados HTTP de CORS (o su ausencia).

Otra forma es usar las herramientas de desarrollo F12 del explorador. La ventana de consola de los exploradores modernos entrega un mensaje de error útil cuando una llamada AJAX no funciona debido a CORS.

Del lado servidor El marco CORS mismo proporciona mensajes de traza detallados mediante las funciones de traza de Web API. Mientras haya un ITraceWriter registrado en Web API, el marco CORS emitirá mensajes con información sobre el proveedor de directivas que se seleccionó, la directiva que se usó y los encabezados HTTP de CORS que se emitieron. Para obtener más información sobre el proceso de traza con Web API, consulte la documentación de Web API en MSDN.

Una característica muy solicitada

CORS ha sido una característica extremadamente solicitada desde hace bastante tiempo, que finalmente se integró en Web API. Este artículo se concentró en gran medida en los detalles mismos de CORS, pero estos conocimientos son esenciales para implementar y depurar CORS. Armado con estos conocimientos, debería resultarle fácil usar las funciones de CORS en Web API para permitir llamadas de origen cruzada en sus propias aplicaciones.

Brock Allen es un consultor especializado en Microsoft .NET Framework, desarrollo web y la seguridad basada en la web. También es instructor de la empresa de entrenamiento DevelopMentor, consultor asociado de thinktecture GmbH & Co. KG, contribuidor en los proyectos de código abierto de thinktecture y contribuidor a la plataforma ASP.NET. Puede encontrarlo en su sitio web, brockallen.com, o enviarle un mensaje de correo electrónico a brockallen@gmail.com.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Yao Huan Lin (Microsoft)
Yao Huang Lin (yaohuang@microsoft.com) es desarrollador de software en el equipo de ASP.NET Web API de Microsoft. Ha trabajado en muchos componentes de .NET Framework, como ASP.NET, Windows Communication Foundation (WCF) y Windows Workflow Foundation (WF).