Executando verificação de segurança...
10

[ Conteúdo ] O que é CORS e porque ele é tão importante para Web

introdução

Hoje venho apresenta-los um dos assuntos mais importantes e que também é muito negligênciado pelo os iniciantes. O intuito aqui é fazer um resumo mastigado do meu entendimento sobre CORS. Não irei me aprofundar muito em coisas que não fazem parte do conceito de CORS, mas se relacionam com ele, como Same-Origin Policy. Sem mais delongas.

O que é CORS e para quê serve?

Cross-Origin Resource Sharing (CORS) é um HTTP header que permite diversas coisas que antes não eram possíveis, como ver a posta, a origem do request, scheme, domínios, dentre diversas outras coisas. Tudo isso que citei, é lido pelo o servidor, assim, fazendo uma verificação se as informações são correspondentes ao que foi configurado. Vou deixar mais claro: Quando fazemos um Request usando CORS, antes de enviar o nosso request, internamente o navegador vai enviar um "preflight" que é um header com diversos paramêtros que vão ser enviados para o servidor ler, e caso esses paramêtros esteja de acordo com o que foi configurado no servidor, o seu request vai ser realizado. Isso é uma técnica de seguraça muito importante que filta bem quais domínions, portas, dentre outras coisas podem ter acesso as informações contidas no Back-end

Outro ponto importante é que CORS permite a requisição de recursos entre navegadores e servidores, que antes, era limitado pelo o sistema Same-origin Policy que evitava requests a partir de scripts com origins diferentes. Todos os navegadores moderno possui CORS integrado a suas APIs de requisições incluído (XMLHttprequest, fetch)

Talvez você esteja na dúvida ainda sobre o porquê do surgimento de CORS. Apesar de eu já ter tido de forma mesmo clara na explicação acima, CORS veio como uma forma de deixar os requests mais seguros e faciltar a requisição de recursos entre navegadores e servidores, sendo um passo muito importante para a Web!

Quais tipos de Request utilizando CORS?

CORS é amplamente implementado por diversos tipos de request e vou listar alguns deles:

  • Utilizando fetch e XMLHttpRequest utilizando CORS por natureza, mas podem ser configurado para não utilizar CORS caso necessário (não recomendado);
  • Com WebFonts quand utilizamos @fontFace no CSS (fiquei supreso também);
  • WebGL texture utiliza de CORS por natureza
  • Imagens e audios para canvas utilizam CORS quando utilizando: drawImage();
  • CSS Shapes from images;

Visão geral do Funcionamento:

CORS trabalha enviando um "preflight" que é um novo header enviado antes do seu request chegar no servidor. Assim o servidor vai checar esse novo header que é chamado de OPTION e vai verificar se é permitido aquela origem, porta, esquema, dentre outras coisas acessarem aquelas informações. HTTP requests que causam efeitos colaterais no servidor (geralmente métodos diferentes de GET e POST com certos tipos de MIME) EXIGÊM um "preflight" para o servidor verificar se realmente é permitido tais ações. CORS também incluí a informação que avisa ao cliente se as credenciais vão ou não serem enviadas.

Quando CORS passa por uma falha, os detalhes do erro não são mostrados por segurança, apenas sabemos que algo deu errado. Caso queira mais informações sobre o erro, pode olhar o console, que vai ter informações adicionais, mas não completas (por razões de segurança);

Utilizando CORS na prática

Os seguintes exemplos vou utilizar XMLHttpRequest pois era assim que estava na documentação, então em casos de você procurar mais informações, não vai ter muita diferença. Vale lembrar que o mesmo vale para Fetch

O que é um simples request?

Antes de processeguir, é extremamente importante eu falar sobre este assunto, que é um simples requests.

O que é uma simples Request? É um request que não envia um "preflight" pois não é necessáro. Todas as médidas de seguranças são tomadas internamente pelo o navegador sem precisar de um "preflight".

Isso se dá porquê não tem a "necessidade". Utilizando métodos simples como GET, POST, o navegador inclui camadas de seguranças suficientes para nos proteger, diferente de request mais complexos que exigem um "preflight" com mais camada de segurança. Isso é uma otimização feita por parte dos navegadores e servidores.

Existem certos requesitos que fazem um request ser um semples request:

O método utilizado é um desses:

  • GET
  • POST
  • HEAD

Os cabeçalhos são limitados a:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type (limited to certain values, such as application/x-www-form-urlencoded, - multipart/form-data, or text/plain)

O tipo de conteúdo permitido são:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain
    Se seu request cobre esses critérios, não vai ocorrer um "preflight", mas pode ficar tranquilo que o navegador irá incluir as camadas de seguranças necessárias.

Aqui está um exemplo com fetch (apenas esse será com fetch ) para visualizar melhor:

fetch('https://api.example.com/data', {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
  },
})
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There was a problem with the fetch operation:', error);
  });

Este código é um simples request, e cabe a você descobrir o porquê como exercícios :)

Agora vamos da uma olha em como seria um exemplo concreto de um simples request em prática.

Vamos supor que o https://foo.example quer pegar algum conteúdo de: https://bar.other

const xhr = new XMLHttpRequest();
const url = "https://bar.other/resources/public-data/";

xhr.open("GET", url);
xhr.onreadystatechange = someHandler;
xhr.send();

Agora vamos dá uma olhada no que o navegador vai enviar para o servidor:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example

Note que a propriedade origin mostra a URL que está fazendo o request. Agora vamos dá uma olhada na resposta do servidor:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[…XML Data…]

Vamos quebrar a resposta do servidor e vamos analisar como ele nos respondeu:
Primeiro, notamos que o servidores nos enviou a seguinte informação:Access-Control-Allow-Origin com um valor de: * o que significa que qualquer origem pode acessar as informações no servidor.

Access-Control-Allow-Origin: *

UMa coisa interessante sobre isso é que podemos limitar quem pode acessar as informações no servidor atráves desse paramêtro:

Access-Control-Allow-Origin: https://foo.example

Se fosse algo assim, https://bar.other só aceitaria a origem https://foo.example. Se fosse diferente, lançaria um erro e encerraria o request.

Quando utilizando credenciais a origem deve ser específicada obrigatoriamente e não pode conter o valor coringa *.

E aqui encerra sobre simples request. Vamos dá uma olhada agora em "preflight" request.

"preflight" Request

Ao contrário do simples request, um request com "preflight" é uma requisição que geralmente é mais complexa e possui mais camadas de segurança como consequência. Quando fazemos uma resquest "preflight", o navegador vai utilizar o método OPTION para enviar um novo header antes da requisição com o intuito de verificar se é seguro para enviar as informações.

O seguinte exemplo é um "preflight" request:

const xhr = new XMLHttpRequest();
xhr.open("POST", "https://bar.other/doc");
xhr.setRequestHeader("X-PINGOTHER", "pingpong");
xhr.setRequestHeader("Content-Type", "text/xml");
xhr.onreadystatechange = handler;
xhr.send("<person><name>Arun</name></person>");

vamos dá uma olhada como funciona a comunicação entre o servidor e o cliente.

OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

Da linha 1 a 10 é o "preflight" request feito com o método OPTION. O método OPTION tem apenas como função enviar algumas informações para checkar a segurança da requisição, e não é possível alterar essas informações, assim garantido mais uma camada de segurança.

Quero dá ênfase nos dos seguintes paramêtros que aparecem nas linhas 9 e 10:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method fala pro servidor que quando for aprovado o "preflight", o request vai ser feito usando o método POST e nenhum outro além desse. Sendo uma camada de segurança evitando casos onde o criminoso possa interceptar o sinal e alterar para o método GET e obter informações confidenciais.
Access-Control-Request-Headers faça pro servidor que quando o "preflight" for aprovado, vai utilizar os headers: X-PINGOTHER e Content-Type custom headers. Agora o servidor tem a oportunidade de verificar se aceita tais condições.

Nas linhas 12 a 21 é a resposta do servidor que aprova o header e o método:

Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

O servidor responde com: Access-Control-Allow-Origin: https://foo.example que restrige o acesso e também informa que os método POST, GET e OPTIONS são aceitos: Access-Control-Allow-Methods: POST, GET, OPTIONS e por último envia os headers aceitos que são: X-PINGOTHER, Content-Type: Access-Control-Allow-Headers: X-PINGOTHER, Content-Type

Opa, não podemos esquecer do: Access-Control-Max-Age que indica em segundos o tempo que uma resposta pode ser armazenada em cache, variando o valor padrão entre navegadores. Neste caso é 24 horas

UMa vez que tudo ocorre como esperado, o request ocorre:

POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache

<person><name>Arun</name></person>

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some XML payload]

Request with credentials

Request com credenciais são diferentes em alguns aspectos. Por padrão, as credenciais nunca são incluídas, a menos que você as faça manualmente.

Neste exemplo o conteúdo original carregado é de https://foo.example que faz uma requisição simples com o método GET para https://bar.other que seta cookies. O código ficaria mais ou menos assim:

const invocation = new XMLHttpRequest();
const url = "https://bar.other/resources/credentialed-content/";

function callOtherDomain() {
  if (invocation) {
    invocation.open("GET", url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send();
  }
}

Observe a propriedade: invocation.withCredentials = true; que é responsável por enviar as credenciais indicado se é incluído ou não com um valor booleano. Se o servidor não responder com Access-Control-Allow-Credentials: true as credenciais serão ignoradas.

Aqui está um exemplo da comunicação entre o servidor e client:

GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

[text/plain payload]

Por mais que na linha 10 esteja explícido o armazenamento de cookies, se o servidor responder diferente de Access-Control-Allow-Credentials: true, será ignorado.

As credenciais NUNCA serão incluídas com o método OPTION, terá apenas: Access-Control-Allow-Credentials: true para indicar que o request pode conter credenciais.

Alguns pontos que devemos nos ater ao trabalhar com credenciais:

  • Access-Control-Allow-Origin deve ser uma específica e nunca uma coringa * por exemplo: Access-Control-Allow-Origin: https://example.com
  • Access-Control-Allow-Headers deve ser uma específica e nunca uma coringa * por exemplo: Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • Access-Control-Allow-Methods deve ser uma específica e nunca uma coringa * por exemplo: Access-Control-Allow-Methods: POST, GET
  • Access-Control-Expose-Headers deve ser uma específica e nunca uma coringa * por exemplo: Access-Control-Expose-Headers: Content-Encoding, Kuma-Revision
    Caso tente enviar credenciais ou consultar e não compra uma dessas obrigações que citei acima, a o resquet vai ser bloqueado e lançara um erro.

Conclusão

Cometi algum equívoco? Me corrijam! Qualquer dúvida, fiquem a vontade para perguntar.

Carregando publicação patrocinada...
6

Corrigindo, o CORS não é um header. É um mecanismo dos browsers, que utiliza headers para seu controle. Há uma sutil diferença, e é importante deixar claro que ele é valido apenas para browsers, e ignorado completamente em clientes HTTP como curl ou wget, e também pode ser desativado manualmente nos browsers (pelo usuário, jamais pelo site/script)

1

Boas observações! Acabei gerando essa ambiguidade. Sobre as outras informações eu nem sabia... Já é um aprendizado. Obrigado por compartilhar!