Compartilhar via


API Web ASP.NET

Suporte do CORS na API Web 2 ASP.NET

Brock Allen

O CORS (Cross-Origin Resource Sharing, Compartilhamento de recursos entre origens) é uma especificação do World Wide Web Consortium (W3C) (normalmente considerada parte do HTML5) que permite ao JavaScript superar a restrição de segurança da política de mesma origem imposta por navegadores. A política de mesma origem significa que o JavaScript pode fazer apenas chamadas AJAX de volta para a mesma origem da página da Web que a contém (em que “origem” é definida como a combinação de nome do host, protocolo e número de porta). Por exemplo, JavaScript em uma página da Web de http://foo.com não pode fazer chamadas AJAX para http://bar.com (ou para http://www.foo.com, https://foo.com ou http://foo.com:999, no caso).

O CORS relaxa essa restrição permitindo que os servidores indiquem quais origens têm permissão para chamá-los. O CORS é imposto por navegadores, mas devem ser implementados no servidor, e a versão mais recente da API Web 2 ASP.NET tem suporte total do CORS. Com a API Web 2, é possível configurar uma política para permitir que clientes JavaScript de uma origem diferente acessem suas APIs.

Noções básicas do CORS

Para usar os novos recursos do CORS na API Web, é útil entender os detalhes do próprio CORS, pois a implementação da API Web é fiel à especificação. Esses detalhes podem parecer pretensiosos agora, mas serão úteis mais tarde para entender as configurações disponíveis na API Web—e quando você estiver depurando o CORS eles o ajudarão a corrigir o problema com mais rapidez.

O mecanismo geral do CORS é tal que quando o JavaScript estiver tentando fazer uma chamada AJAX entre origens o navegador “perguntará” ao servidor se isso é permitido enviando cabeçalhos na solicitação HTTP (por exemplo, Origin). O servidor indica o que é permitido retornando cabeçalhos HTTP na resposta (por exemplo, Access-Control-Allow-Origin). Essa verificação de permissão é feita para cada URL distinta que o cliente invoca, o que significa que URLs diferentes podem ter permissões diferentes.

Além da origem, o CORS permite que um servidor indique quais métodos HTTP são permitidos, quais cabeçalhos de solicitação HTTP um cliente pode enviar, quais cabeçalhos de resposta HTTP um cliente pode ler e se o navegador tem permissão para enviar ou receber credenciais automaticamente (cookies ou cabeçalhos de autorização). Cabeçalhos de solicitação e resposta adicionais indicam quais desses recursos são permitidos. Esses cabeçalhos estão resumidos na Figura 1 (observe que alguns recursos não têm nenhum cabeçalho enviado na solicitação — apenas a resposta).

Figura 1 Cabeçalhos HTTP do CORS

Permissão/Recurso Cabeçalho da solicitação Cabeçalho da resposta
Origem Origin Access-Control-Allow-Origin
Método HTTP Access-Control-Request-Method Access-Control-Allow-Method
Cabeçalhos da solicitação Access-Control-Request-Headers Access-Control-Allow-Headers
Cabeçalhos da resposta   Access-Control-Expose-Headers
Credenciais   Access-Control-Allow-Credentials
Resposta de simulação do cache   Access-Control-Max-Age

Os navegadores podem pedir ao servidor essas permissões de duas maneiras diferentes: solicitações CORS simples e solicitações CORS de simulação.

Solicitações CORS simples Vejamos um exemplo de uma solicitação CORS simples:

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

E a resposta:

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}

A solicitação é uma entre origens de http://localhost:55912 para http://localhost, e o navegador adiciona um cabeçalho HTTP Origin à solicitação para indicar ao servidor a origem que está chamando. O servidor responde com um cabeçalho de resposta Access-­Control-Allow-Origin indicando que essa origem é permitida. O navegador impõe a política do servidor e o JavaScript receberá seu retorno de chamada bem-sucedido normal.

O servidor pode responder com o valor da origem exato da solicitação ou com um valor de “*” indicando que qualquer origem é permitida. Se o servidor não tivesse permitido a origem que está chamando, então, o cabeçalho Access-Control-Allow-Origin estaria simplesmente ausente e o retorno de chamada com erro do JavaScript que está chamando seria invocado.

Observe que com uma simples solicitação CORS a chamada no servidor ainda é invocada. Isso pode ser surpreendente se ainda estiver aprendendo sobre o CORS, mas esse comportamento não é diferente de um cenário em que o navegador havia construído um elemento <form> e feito uma solicitação POST normal. O CORS não impede que a chamada seja invocada no servidor; em vez disso, ele impede o JavaScript que está chamando de receber os resultados. Se desejar impedir que o chamador invoque o servidor, então, você deveria implementar alguma forma de autorização em seu código de servidor (possivelmente com o atributo do filtro de autorização [Authorize]).

O exemplo anterior é conhecido como uma solicitação CORS simples porque o tipo de chamada AJAX do cliente foi um GET ou um POST; o Content-Type foi um dos application/x-www-form-­urlencoded, multipart/form-data ou text/plain; e não houve nenhum cabeçalho de solicitação adicional enviado. Se a chamada AJAX fosse outro método HTTP, o Content-Type fosse algum outro valor ou o cliente quisesse enviar cabeçalhos de solicitação adicionais, então, a solicitação seria considerada uma solicitação de simulação. O mecanismo das solicitações de simulação é um pouco diferente.

Solicitações CORS de simulação Se uma chamada AJAX não for uma solicitação simples, então, ela irá requerer uma solicitação CORS de simulação, que é simplesmente uma solicitação HTTP adicional ao servidor para obter permissão. A solicitação de simulação é feita automaticamente pelo navegador e usa o método OPTIONS HTTP. Se o servidor responder com êxito à solicitação de simulação e conceder permissão, então, o navegador executará a chamada AJAX real que o JavaScript está tentando fazer.

Se o desempenho for uma preocupação (e quando não é?), então, o resultado dessa solicitação de simulação poderá ser armazenado em cache pelo navegador com a inclusão do cabeçalho Access-Control-Max-Age na resposta de simulação. O valor contém o número de segundos pelos quais as permissões podem ser armazenadas em cache.

Vejamos um exemplo de uma solicitação CORS de simulação:

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: */*

E a resposta de simulação:

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

Vejamos a solicitação AJAX real:

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}

E a resposta 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 nesse exemplo que uma solicitação CORS de simulação é acionada porque o método HTTP é PUT e o cliente precisa enviar o cabeçalho Content-Type para indicar que a solicitação contém application/json. Na solicitação de simulação (além de Origin) os cabeçalhos de solicitação Access-Control-Request-Method e Access-Control-Request-Headers são usados para pedir permissão para o tipo de método HTTP e o cabeçalho adicional que o cliente deseja enviar.

O servidor concedeu permissão (e definiu uma duração do cache de simulação) e, em seguida, o navegador permitiu a chamada AJAX real. Se o servidor não concedesse permissão a qualquer dos recursos solicitados, então, o cabeçalho de resposta correspondente teria estado ausente, a chamada AJAX não teria sido feita e, em seu lugar, o retorno de chamada com erro do JavaScript teria sido invocado.

As solicitações e respostas HTTP anteriores foram feitas com o Firefox. Se você fosse usar o Internet Explorer, então, notaria um cabeçalho Accept adicional sendo solicitado. Se você fosse usar o Chrome, notaria Accept e Origin adicionalmente solicitados. Curiosamente, você não verá Accept ou Origin no Access-Control-­Allow-Headers, pois a especificação diz que eles estão implícitos e podem ser omitidos (o que a API Web faz). É um assunto de debate se Origin e Accept realmente precisam ser solicitados, mas considerando como esses navegadores funcionam atualmente, sua política CORS da API Web muito provavelmente precisará incluí-los. É uma pena que os fornecedores de navegadores não pareçam ser consistentes em sua interpretação da especificação.

Cabeçalhos de resposta É fácil dar a um cliente permissão para acessar cabeçalhos de resposta usando o cabeçalho de resposta Access-Control-Expose-Headers. Vejamos um exemplo de uma resposta HTTP que permite ao JavaScript que faz a chamada acessar a “barra” do cabeçalho de resposta personalizada:

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}

O cliente JavaScript pode simplesmente usar a função getResponseHeader do XMLHttpRequest para ler o valor. Este é um exemplo de uso de jQuery:

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

Credenciais e autenticação Possivelmente, o aspecto mais confuso do CORS tenha a ver com credenciais e autenticação. Geralmente, a autenticação com APIs Web pode ser feita com um cookie ou com um cabeçalho Authorization (há outras formas, mas essas duas são as mais comuns). Na atividade normal do navegador, se uma delas foi estabelecida anteriormente, então, o navegador passará implicitamente esses valores para o servidor em solicitações subsequentes. No entanto, com o AJAX entre origens, essa passagem implícita de valores deve ser solicitada explicitamente no JavaScript (com o sinalizador withCredentials no XMLHttpRequest) e deve ser explicitamente permitida na política CORS do servidor (com o cabeçalho de resposta Access-Control-Allow-Credentials).

Segue um exemplo de um cliente JavaScript definindo o sinalizador withCredentials com jQuery:

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

O sinalizador withCredentials faz duas coisas: Se o servidor emite um cookie, o navegador pode aceitá-lo; se o navegador tem um cookie, ele pode enviá-lo ao servidor.

Aqui está um exemplo da resposta HTTP permitindo credenciais:

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

O cabeçalho de resposta Access-Control-Allow-Credentials faz duas coisas: Se a resposta tem um cookie, o navegador pode aceitá-lo; e se o navegador enviou um cookie na solicitação, o cliente JavaScript pode receber os resultados na chamada. Em outras palavras, se o cliente definir withCredentials, então, o cliente verá um retorno de chamada bem-sucedido no JavaScript apenas se o servidor (na resposta) permitir credenciais. Se withCredentials foi definido e o servidor não permitir credenciais, o cliente não obterá acesso aos resultados e o retorno de chamada com erro do cliente será invocado.

O mesmo conjunto de regras e comportamentos se aplicam se o cabeçalho Authorization é usado em vez de cookies (por exemplo, ao usar a autenticação Básica ou Integrada do Windows. Observação interessante sobre o uso de credenciais e o cabeçalho Authorization: O servidor não tem de conceder explicitamente o cabeçalho Authorization no cabeçalho de resposta CORS Access-Control-Allow-Headers.

Observe que com o cabeçalho de resposta CORS Access-Control-Allow-Credentials, se o servidor emitir esse cabeçalho, então, o valor curinga de “*” não poderá ser usado para Access-Control-Allow-Origin. Em seu lugar, a especificação CORS exige que a origem explícita seja usada. A estrutura da API Web manipula tudo isso para você, mas menciono isso aqui porque você poderá notar esse comportamento durante a depuração.

Há uma mudança interessante na discussão de credenciais e autenticação. A descrição até aqui tem sido para o cenário em que o navegador está enviando implicitamente credenciais. É possível para um cliente JavaScript enviar explicitamente credenciais (novamente, normalmente com o cabeçalho Authorization). Se esse for o caso, nenhum dos comportamentos ou regras mencionados anteriormente relacionados a credenciais se aplica.

Para esse cenário, o cliente definiria explicitamente o cabeçalho Authorization na solicitação e não precisaria definir withCredentials no XMLHttpRequest. Esse cabeçalho dispararia uma solicitação de simulação e o servidor precisaria permitir o cabeçalho Authorization com o cabeçalho de resposta CORS Access-Control-Allow-Headers. Ainda, o servidor não precisaria emitir o cabeçalho de resposta CORS Access-Control-­Allow-Credentials.

Aqui está como esse código cliente ficaria para definir explicitamente o cabeçalho Authorization:

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

Vejamos a solicitação de simulação:

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: */*

Vejamos a resposta de simulação:

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

Definir explicitamente um valor de token no cabeçalho Authorization é uma abordagem mais segura porque você evita a possibilidade de ataques de solicitação intersite forjada (CSRF). É possível ver essa abordagem nos novos modelos de SPA (Single-Page Application, aplicativo de página única) do Visual Studio 2013.

Agora que você já viu os fundamentos do CORS no nível HTTP, mostrarei como usar a nova estrutura CORS para emitir esses cabeçalhos na API Web.

Suporte do CORS na API Web 2

O suporte do CORS na API Web é uma estrutura completa para permitir que um aplicativo defina as permissões de solicitações CORS. A estrutura gira em torno do conceito de uma política que permite especificar os recursos CORS a serem permitidos em qualquer determinada solicitação no aplicativo.

Primeiro, para obter a estrutura CORS, é preciso fazer referência às bibliotecas CORS em seu aplicativo API Web (elas não são referenciadas por padrão em qualquer dos modelos da API Web do Visual Studio 2013). A estrutura CORS da API Web está disponível via NuGet como o pacote Microsoft.AspNet.WebApi.Cors. Se não estiver usando o NuGet, também está disponível como parte do Visual Studio 2013, e você precisará fazer referência a dois assemblies: System.Web.Http.Cors.dll e System.Web.Cors.dll (em meu computador eles estão localizados em C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages).

Continuando, para expressar a política, a API Web fornece uma classe de atributo personalizada chamada EnableCorsAttribute. Essa classe contém propriedades para as origens permitidas, métodos HTTP, cabeçalhos de solicitação, cabeçalhos de resposta e se credenciais são permitidas (o que modela todos os detalhes da especificação CORS discutidos anteriormente).

Finalmente, para que a estrutura CORS da API Web processe solicitações CORS e emita os cabeçalhos de resposta CORS apropriados, ela deve olhar cada solicitação para o aplicativo. A API Web tem um ponto de extensibilidade para tal interceptação por meio de manipuladores de mensagens. De forma adequada, a estrutura CORS da API Web implementa um manipulador de mensagens chamado CorsMessageHandler. Para solicitações CORS, ele consultará a política expressa no atributo do método sendo invocado e emitirá os cabeçalhos de resposta CORS apropriados.

EnableCorsAttribute A classe EnableCorsAttribute é como um aplicativo pode expressar sua política CORS. A classe EnableCorsAttribute tem um construtor sobrecarregado que pode aceitar três ou quatro parâmetros. Os parâmetros são (em ordem):

  1. Lista das origens permitidas
  2. Lista dos cabeçalhos de solicitação permitidos
  3. Lista dos métodos HTTP permitidos
  4. Lista dos cabeçalhos de resposta permitidos (opcional)

Há também uma propriedade para permitir credenciais (Supports­Credentials) e outra para especificar o valor de duração do cache de simulação (PreflightMaxAge).

A Figura 2 mostra um exemplo de aplicação do atributo EnableCors a métodos individuais em um controlador. Os valores sendo usados para as várias configurações de política CORS devem corresponder às solicitações e respostas CORS que foram mostradas nos exemplos anteriores.

Figura 2 Aplicando o atributo EnableCors a métodos de ação

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 cada um dos parâmetros do construtor é uma cadeia de caracteres. Diversos valores são indicados especificando uma lista separada por vírgulas (como está especificado nos cabeçalhos de solicitação permitidos na Figura 2). Se desejar permitir todas as origens, cabeçalhos de solicitação ou métodos HTTP, é possível usar um “*” como o valor (você ainda precisa ser explícito para cabeçalhos de resposta).

Além de aplicar o atributo EnableCors no nível do método, você também pode aplicá-lo no nível da classe ou globalmente ao aplicativo. O nível no qual o atributo é aplicado configura o CORS para todas as solicitações naquele nível e abaixo no código de sua API Web. Assim, por exemplo, se aplicado no nível do método, a política se aplicará apenas a solicitações para esta ação, enquanto se aplicado no nível da classe, a política será para todas as solicitações para este controlador. Finalmente, se aplicado globalmente, a política será para todas as solicitações.

A seguir encontramos outro exemplo de aplicação do atributo no nível da classe. As configurações usadas nesse exemplo são bastante permissivas porque o curinga é usado para as origens permitidas, os cabeçalhos de solicitação e os métodos HTTP.

[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);
  }
}

Se houver uma política em diversas localidades, o atributo “mais próximo” é usado e os outros são ignorados (portanto, a precedência é método, então classe e então global). Se você aplicou a política em um nível mais alto, mas então deseja excluir uma solicitação em um nível mais baixo, é possível usar outra classe de atributo chamada DisableCorsAttribute. Em essência, esse atributo é uma política com nenhuma permissão permitida.

Se você tiver outros métodos no controlador em que você não deseja permitir CORS, é possível usar uma de duas opções. Primeiro, você pode ser explícito na lista de métodos HTTP, conforme mostrado na Figura 3. Ou pode deixar o curinga, mas excluir o método Delete com o atributo DisableCors, conforme mostrado na Figura 4.

Figura 3 Usando valores explícitos para 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 Usando o 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 O CorsMessageHandler deve estar habilitado para que a estrutura CORS execute seu trabalho de interceptação de solicitações para avaliação da política CORS e emissão dos cabeçalhos de resposta CORS. Habilitar o manipulador de mensagens é normalmente feito na classe de configuração da API Web do aplicativo invocando o método de extensão EnableCors:

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

Se você desejar fornecer uma política CORS global, é possível passar uma instância da classe EnableCorsAttribute como um parâmetro para o método EnableCors. Por exemplo, o código a seguir iria configurar uma política CORS permissiva globalmente no aplicativo:

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

Como com qualquer manipulador de mensagens, como alternativa, o CorsMessageHandler pode ser registrado por rota em vez de globalmente.

Portanto, é tudo para a estrutura CORS básica e “pronta para uso” na API Web 2 ASP.NET. Uma coisa boa sobre a estrutura é que ela é extensível para cenários mais dinâmicos, nos quais darei uma olhada a seguir.

Personalizando a política

Deveria estar óbvio dos exemplos anteriores que a lista de origens (se o curinga não estiver sendo usado) é uma lista estática compilada no código da API Web. Embora isso possa funcionar durante o desenvolvimento ou em cenários específicos, não é suficiente se a lista de origens (ou outras permissões) precisar ser determinada dinamicamente (digamos, de um banco de dados).

Felizmente, a estrutura CORS na API Web é extensível de tal modo que dar suporte a uma lista de origens dinâmica é fácil. Na realidade, a estrutura é tão flexível que há duas abordagens gerais para personalizar a geração de política.

Atributo de política CORS personalizado Uma abordagem para habilitar uma política CORS dinâmica é desenvolver uma classe de atributo personalizada que possa gerar a política de alguma fonte de dados. Essa classe de atributo personalizada pode ser usada em vez da classe EnableCorsAttribute fornecida pela API Web. Essa abordagem é simples e mantém o aspecto refinado de poder aplicar um atributo a classes e métodos específicos (e não aplicá-lo a outros), conforme necessário.

Para implementar essa abordagem, simplesmente crie um atributo personalizado semelhante à classe EnableCorsAttribute existente. O foco principal é a interface ICorsPolicyProvider, que é responsável pela criação de uma instância de uma CorsPolicy para qualquer determinada solicitação. A Figura 5 contém um exemplo.

Figura 5 Um atributo de política CORS 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;
  }
}

A classe CorsPolicy tem todas as propriedades para expressar as permissões CORS a serem concedidas. Os valores usados aqui são apenas um exemplo, mas provavelmente eles poderiam ser preenchidos dinamicamente de uma consulta de banco de dados (ou de qualquer outra fonte).

Fábrica de provedores de política personalizada A segunda abordagem geral para a criação de uma política CORS dinâmica é criar uma fábrica de provedores de política personalizada. Essa é a parte da estrutura CORS que obtém o provedor de política da solicitação atual. A implementação padrão da API Web usa os atributos personalizados para descobrir o provedor de política (como visto anteriormente, a própria classe do atributo foi o provedor de política). Essa é outra parte conectável da estrutura CORS, e você implementaria sua própria fábrica de provedores de política se desejasse usar uma abordagem de política diferente de atributos personalizados.

A abordagem baseada em atributo descrita anteriormente fornece uma associação implícita de uma solicitação a uma política. Uma abordagem de fábrica de provedores de política personalizada é diferente da abordagem de atributo porque requer que sua implementação forneça a lógica para corresponder a solicitação recebida a uma política. Essa abordagem tem maior granularidade, pois é essencialmente uma abordagem centralizada para obtenção de uma política CORS.

A Figura 6 mostra um exemplo de como seria uma fábrica de provedores de política personalizada. O foco principal nesse exemplo é a implementação da interface ICorsPolicyProviderFactory e seu método GetCorsPolicyProvider.

Figura 6 Uma fábrica de provedores de política personalizada

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);
  }
}

A principal diferença nessa abordagem é que fica inteiramente a cargo da implementação determinar a política da solicitação recebida. Na Figura 6, o controlador e a origem poderiam ser usados para consultar os valores da política em um banco de dados. Novamente, essa abordagem é a mais flexível, mas ela requer potencialmente mais trabalho para determinar a política da solicitação.

Para usar a fábrica de provedores de política personalizada, é preciso registrá-la na API Web por meio do método de extensão SetCorsPolicyProviderFactory na configuração da API Web:

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

Contribuições da comunidade em ação

A API Web ASP.NET é uma estrutura de software livre e é parte de um conjunto maior de estruturas de software livre coletivamente chamado de ASP.NET Web Stack, que também inclui MVC, Páginas da Web e outros.

Essas estruturas são usadas para criar a plataforma ASP.NET e têm curadoria da equipe do ASP.NET na Microsoft. Como curadora de uma plataforma de software livre, a equipe do ASP.NET agradece as contribuições da comunidade. A implementação do compartilhamento de recursos entre origens na API Web é uma dessas contribuições.

Ele foi desenvolvido originalmente por Brock Allen como parte da biblioteca de segurança IdentityModel da thinktecture (thinktecture.github.io).

Depuração do CORS

Algumas técnicas vêm à mente para depurar o CORS se (e quando) suas chamadas AJAX entre origens não estiverem funcionando.

Lado do cliente Uma abordagem à depuração é simplesmente usar seu depurador HTTP preferido (por exemplo, Fiddler) e inspecionar todas as solicitações HTTP. Armado do conhecimento obtido anteriormente sobre os detalhes da especificação CORS, você pode normalmente solucionar porque uma solicitação AJAX específica não está recebendo permissão com a inspeção dos cabeçalhos HTTP do CORS (ou a falta deles).

Uma outra abordagem é usar as Ferramentas de Desenvolvedor F12 de seu navegador. A janela do console em navegadores modernos fornece uma mensagem de erro útil quando uma chamada AJAX falha devido ao CORS.

Lado do servidor A própria estrutura CORS fornece mensagens de rastreamento detalhadas com o uso de recursos de rastreamento da API Web. Desde que um ITraceWriter esteja registrado na API Web, a estrutura CORS emitirá mensagens com informações sobre o provedor de política selecionado, a política usada e os cabeçalhos HTTP do CORS emitidos. Para obter mais informações sobre rastreamento da API Web, consulte a documentação da API Web no MSDN.

Um recurso altamente solicitado

O CORS tem sido um recurso bastante solicitado já há algum tempo e finalmente está integrado à API Web. Este artigo está bastante concentrado nos detalhes do próprio CORS, mas este conhecimento é crucial na implementação e depuração do CORS. Armado deste conhecimento, você deve ser capaz de facilmente utilizar o suporte do CORS na API Web para permitir chamadas entre origens em seu aplicativo.

Brock Allen é consultor especializado no Microsoft .NET Framework, desenvolvimento para a Web e segurança baseada na Web. Ele é também instrutor na empresa de treinamento DevelopMentor, consultor associado da thinktecture GmbH & Co. KG, colaborador de projetos de software livre da thinktecture e colaborador da plataforma ASP.NET. Você pode entrar em contato com ele em seu site, brockallen.com ou enviar um email para ele em brockallen@gmail.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Yao Huan Lin (Microsoft)
Yao Huang Lin (yaohuang@microsoft.com) é um desenvolvedor de software na equipe da API Web ASP.NET da Microsoft. Ele trabalhou em muitos componentes do .NET Framework, incluindo ASP.NET, Windows Communication Foundation (WCF) e Windows Workflow Foundation (WF).