All Articles

Política de Cross-Origin

É muito comum uma pessoas que desenvolvem Front-End se depararem com um erro frequente que é parecido com:

Origin http://localhost is not allowed by Access-Control-Allow-Origin

E então vamos procurar na internet como resolver esse problema e nos deparamos com diversas soluções e cada uma funcionando de forma diferente e as vezes só copiamos e colamos e não entendemos muito bem.

Resposta do StackOverflow falando para adicionar um header no servidor

Resposta do StackOverflow falando para adicionar um header no servidor

Pensando neste exemplo de resposta do StackOverflow, a recomendação é adicionar um header no servidor e tudo será resolvido. Se alguém que passou por essa questão e fez apenas o que foi sugerido na imagem, acredito que não resolveu 100% do problema (sim, já aconteceu comigo). Além disso, nem sempre temos acesso a este servidor.

Resposta do StackOverflow para abrir o chrome em modo sem segurança

Resposta do StackOverflow para abrir o chrome em modo sem segurança

Neste outro caso, é recomendado que a pessoa abra o navegador com a segurança desativada que os requests vão funcionar. Já testei esse modo também e é incrível! Tudo funciona perfeitamente! Mas será que todos os usuários do meu site vão fazer isso também? É, acho que não.

Neste artigo vou mostrar um pouco do que é essa política cross-origin, mais conhecida como CORS.


O que é CORS?

Cross-Origin Resource Sharing é a sigla atribuída ao mecanismo dos navegadores que gerencia o compartilhamento de recursos entre diferentes origens.

Por padrão, quando fazemos uma requisição através do XMLHttpRequest ou utilizando a API Fetch do javascript, os navegadores utilizam a política same-origin que só autoriza a troca de recursos entre as mesmas origens (domínios). Se for necessário essa troca entre diferentes origens, é necessário configurar as chamadas para que contenham os cabeçalhos CORS corretos.

A utilização do CORS é importante porque além de poder restringir QUEM pode acessar os recursos de um servidor também pode especificar COMO esses recursos devem ser acessados. Mais para frente retornaremos nesta parte com exemplos.

O que é uma origem?

Uma origem, ou domínio é composto por três partes: protocolo, host e porta.

Definição de Origem ou Domínio: conjunto de protocolo, host e porta

Definição de Origem ou Domínio: conjunto de protocolo, host e porta

Pegando como exemplo o domínio http://exemplo.com acima temos como protocolo HTTP, o host exemplo.com e a porta (oculta) 80. Se for necessário obter um recurso que mude qualquer um destes 3 itens, já não está dentro da mesma origem e, portanto a política same-origin não é mais válida e por isso precisamos configurar as chamadas para utilizarmos a política cross-origin.

Políticas de compartilhamento de recursos

Políticas de compartilhamento de recursos


Como funciona o CORS?

O mecanismo funciona adicionando cabeçalhos HTTP nas respostas dos servidores para que estabeleçam um compartilhamento de recursos seguro entre diferentes origens. Com ele é possível configurar as origens permitidas para acessar o recurso e quais métodos (GET, POST, PUT, DELETE, etc.) elas podem utilizar.

Além disso, quando o site faz uma requisição que pode causar efeitos colaterais no servidor, o navegador realiza uma “pré-requisição” ou preflight request para que o servidor retorne que é possível realizar a requisição que é pedida originalmente e só então ela é enviada ao servidor.

Preflight — Simulação de uma conversa entre o Servidor A e o Servidor B

Preflight — Simulação de uma conversa entre o Servidor A e o Servidor B

A figura acima mostra como seria essa requisição se ela fosse uma conversa entre humanos. O Servidor A está precisando realizar uma ação no Servidor B que causa um efeito colateral nos dados do Servidor B e por isso, é necessário fazer a pré-chamada que é o mesmo que pedir autorização para fazer a ação 𝞪 no recurso 𝞫 sendo a origem 𝟃.

Mas então, pra toda requisição que meu site faz, na verdade são feitas duas requisições? A preflight e a original? Bom, na verdade existem algumas condições que necessitam de fazer as pré-chamadas. Portanto existem dois tipos de requisições:

  • as requisições simples
  • as requisições que exigem preflight.

Requisições Simples

Uma requisição simples é basicamente uma requisição que, em teoria não causa efeitos colaterais no servidor. Além disso tem que atender a todas as condições abaixo:

  • Métodos permitidos: GET, HEAD, POST (com ressalvas relacionadas ao cabeçalho, como mostra os próximos tópicos)
  • Além dos cabeçalhos pré-configurados pela conexão, apenas alguns outros são permitidos, como: Accept, Accept-Language, Content-Language, Content-Type (com ressalvas), DPR, Downlink, Save-Data, Viewport-Width, Width
  • Valores permitidos para Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain

Nos códigos abaixo podemos verificar exemplos de chamada de uma requisição simples e como ela deve ser tratada no servidor.

fetch("http://localhost:8080", {
  method: "GET"
});

Requisição simples — Cliente

app.get("/", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
  res.send("Hello Cors Test");
});

Requisição simples — Servidor

Nesta chamada podemos verificar os cabeçalhos da requisição e não é necessário fazer a pré-requisição pois está dentro das condições anteriores. Contudo, mesmo nas chamadas simples, precisamos configurar o CORS como podemos ver no cabeçalho Access-Control-Allow-Origin.

Cabeçalhos — Requisição simples

Cabeçalhos — Requisição simples

Requisições que exigem pré-requisições

Quando a requisição não atende às condições citadas acima, ela precisa enviar uma pré-requisição que nada mais é que um OPTIONS com algumas informações sobre a requisição original.

fetch("http://localhost:8080", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: { key: "value" }
});

Requisição preflight — Cliente

No caso dessas requisições é necessário configurar os cabeçalhos para a chamada OPTIONS porque ela que realiza a autorização da requisição original.

app.options("/", (req, res) => {
  res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
  res.send(true);
});

app.post("/", (req, res) => {
  res.send("POSTED on root");
});

Requisição preflight — Servidor

As imagens abaixo mostram os cabeçalhos da pré-chamada e da chamada efetiva respectivamente. É possível reparar que, na parte do Request Headers o cliente envia ao servidor que deseja adicionar um cabeçalho não autorizado por padrão — content-type — e também uma ação que pode causar um efeito colateral — POST — e por isso é preciso enviar esses cabeçalhos requisitando esses recursos. Na parte de Response Headers é possível observar que o servidor autoriza a origem http://localhost:3000 a acessar seu recurso e também autoriza o cliente a adicionar o cabeçalho content-type.

Preflight Headers Post Headers

Cabeçalhos — Preflight e Efetivo


Boas práticas utilizando CORS

É claro que quando conhecemos mais sobre o que estamos usando, configurando e trabalhando consequentemente já teremos uma nova visão e um senso crítico para tal. Mas como tudo, as configurações de CORS também exigem algumas boas práticas para manter a segurança e consistência da comunicação entre diferentes origens.

  • Na configuração de Access-Control-Allow-Origin, ao invés de utilizar ”*” que libera qualquer origem de acessar o seu servidor, coloque exatamente a(s) origens que realmente devem ter acesso.
  • O princípio do CORS é manter sua conexão segura, e por isso se liberarmos qualquer origem, qualquer método e/ou qualquer cabeçalho, este princípio não será atingido. Portanto precisamos deixar nossas requisições seguras e apenas autorizar as origens, métodos e cabeçalhos necessários para a conexão.
  • Existem alguns workarounds/gambiarras, como vimos nos posts do stack overflow, ou resolver apenas no lado do cliente usando técnicas como jsonp do jquery e no-cors na API Fetch (não é recomendável). O ideal é estar configurado com o mínimo de abertura, apenas o necessário.

Conclusão

Bom, o objetivo desse artigo é compartilhar alguns conhecimentos que adquiri ao me deparar com problemas relacionados a política cross-origin e, como sei que pra mim foi difícil entender o problema e encontrar soluções, imagino que outras pessoas também tenham essa dificuldade e por isso eu resolvi ajudar com esse texto. Espero que tenham gostado e comentários e/ou informações a acrescentar são sempre bem vindos!

Referências

Agradecimento especial a Vanessa e Diogo por revisarem esse texto ❤