Front End

10 mai, 2019

Política de Cross-Origin

Publicidade

É muito comum as pessoas que desenvolvem front-end se depararem com um erro frequente, parecido com isso:

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

Ao procurarmos a solução desse problema na internet, nos deparamos com diversas soluções – cada uma funcionando de forma diferente e às vezes só copiamos e colamos e não entendemos muito bem.

Resposta do Stack Overflow falando para adicionar um header no servidor

Pensando neste exemplo de resposta do Stack Overflow, a recomendação é adicionar um header no servidor e tudo será resolvido.

Se alguém passou por essa questão e fez apenas o que foi sugerido na imagem, acredito que não tenha conseguido resolver 100% do problema (sim, já aconteceu comigo). Além disso, nem sempre temos acesso a este servidor.

Resposta do Stack Overflow 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ária 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 pra 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

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 três itens, já não está dentro da mesma origem e, portanto, a política same-origin não é mais válida – por isso precisamos configurar as chamadas para utilizarmos a política cross-origin.

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 seguros 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

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, para 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:

  • Requisições simples
  • 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 à 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:

Requisição simples – Cliente

fetch('http://localhost:8080', {
      method: 'GET'
    }).then(response => {
      // Do something with response
    }).catch(error => {
      // Handle error
    })

Requisição simples – Servidor

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

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

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 é do que um OPTIONS com algumas informações sobre a requisição original.

Requisição preflight — Cliente

fetch('http://localhost:8080', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: { key: 'value' }
    }).then(response => {
      // Do something with response
    }).catch(error => {
      // Handle error
    })

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.

Requisição preflight – Servidor

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

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.

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 a 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 as 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 artigos 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 deste artigo é compartilhar alguns conhecimentos que adquiri ao me deparar com problemas relacionados à 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.

Por isso eu resolvi ajudar com esse texto. Espero que tenham gostado e comentários e/ou informações são sempre bem-vindos!

Referências