CORS: Entenda como funciona o Cross-Origin Resource Sharing
Cross-Origin Resource Sharing (CORS) é um mecanismo fundamental para permitir que aplicativos web acessem recursos de servidores localizados em origens diferentes daquelas em que estão sendo executados. Veja neste texto, como funciona e como resolver os problemas relacionados.
De cara vamos deixar algo bem claro: CORS é um mecanismo de segurança gerenciado pelo navegador, ou seja, client side. Isso já justifica um comentário comum: "funciona no Postman mas não no navegador". Outro ponto importante, as configurações CORS são feitas no servidor que hospeda o recurso que precisa ser acessado. O servidor define quais origens são permitidas acessar o recurso, enviando os cabeçalhos CORS corretos na resposta.
Dito isto, vamos explorar neste texto o que é o CORS e como podemos tratar seus erros.
Introdução ao CORS
Cross-Origin Resource Sharing, ou CORS, é um mecanismo do navegador disparado quando há uma tentativa de acessar recursos de uma origem diferente daquela da página que os está solicitando.
Exemplo
Considere dois endereços hipotéticos:
- http://pagina.com: retorna uma página HTML com um pequeno JavaScript que busca dados em http://api.com/dados
- http://api.com: API com um único endpoint, /dados, que retorna um JSON
Quando o usuário abre http://pagina.com no navegador e o pequeno script é interpretado, o navegador faz uma requisição ao endereço http://api.com/dados para obter um JSON. Repare que cada recurso, a página HTML e o JSON, tem uma origem diferente. O fato de serem fornecidos por origens diferentes, faz com que o navegador faça as validações CORS.
CORS: funcionamento básico
O princípio básico é que quem está servindo os dados indique no cabeçalho da resposta quais são as regras, fazendo uma analogia, seria algo como "minha casa, minhas regras" ou "meu recurso, minhas regras".
Cabeçalhos HTTP enviados em resposta a solicitações CORS
Os cabeçalhos que indicam essas regras são:
- Access-Control-Allow-Origin
Especifica quais origens têm permissão para acessar o recurso. - Access-Control-Allow-Methods
Especifica quais métodos HTTP são permitidos para acessar o recurso. - Access-Control-Allow-Headers
Especifica quais cabeçalhos HTTP são permitidos na solicitação. - Access-Control-Allow-Credentials
Indica se as credenciais (como cookies e tokens de autenticação) são permitidas na solicitação.
Caso o endereço de destino não informe nenhum dos cabeçalhos acima ou os valores informados impeçam a origem de consumi-los, o navegador pode bloquear a requisição lançando uma mensagem de erro parecida com a seguinte no console do DevTools:
Access to fetch at 'http://api.com/dados' from origin 'http://pagina.com' has been blocked by CORS policy
Na sequência há 2 exemplos que podem ajudar no entendimento e também progredir um pouco mais no assunto.
Exemplo 1: simulando um problema de CORS com uma requisição GET
Preparando o ambiente:
# Cria o diretório
mkdir estudo-cors
cd estudo-cors
# Inicia o pacote NPM
npm init -y
# Instala o express
npm i express
Após executar o script acima, criei um arquivo chamado app.js:
O código acima iniciará uma escuta na porta 80, que é padrão para o protocolo HTTP. Dado que apenas uma aplicação está sendo executada na porta 80, para reproduzir o exemplo mencionado, modifiquei o arquivo hosts do meu computador para incluir as seguintes entradas:
127.0.0.1 pagina.com
127.0.0.1 api.com
# Local do arquivo hosts:
# MacOS: /etc/hosts
# Windows: C:\Windows\System32\drivers\etc
Com isto, quando digitar pagina.com ou api.com no meu navegador, o IP correspondente será meu próprio computador, ou seja, http://pagina.com e http://api.com, apontarão para a aplicação Node.js que está em execução na porta 80.
Erro CORS
Para testar, primeiramente executei o código com o comando node app.js
e depois abri o endereço http://pagina.com no meu navegador. Conforme esperado e já comentado, pegamos um erro relacionado a CORS:
Resolvendo o problema de CORS
Neste exemplo, o site http://pagina.com precisa acessar dados em http://api.com/dados. Então o dono do recurso, http://api.com, precisa incluir no cabeçalho de resposta quem pode consumir este recurso. Para fazer isto, basta incluir na resposta da requisição o cabeçalho Access-Control-Allow-Origin.
Este cabeçalho pode ser um endereço específico, como http://pagina.com, ou * que significa nenhuma restrição de origem.
Modificando o código:
Agora http://pagina.com pode consumir recursos de http://api.com/dados:
Este foi um exemplo bem simples, porém real. Essa resolução é válida para os cenários que atendem as seguintes regras:
- Requisições do tipo:
- GET
- HEAD
- POST
- Além dos cabeçalhos definidos automaticamente pelo navegador do usuário, os únicos cabeçalhos que podem ser definidos manualmente são aqueles listados na especificação Fetch:
- Accept
- Accept-Language
- Content-Language
- Content-Type (detalhes abaixo)
- O valor do Content-Type deve ser:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Caso a requisição atenda as regras acima, a resolução proposta neste exemplo funcionará corretamente. Por exemplo:
POST /dados HTTP/1.1
Content-Length: 40
Host: api.com
Origin: http://pagina.com
content-type: application/x-www-form-urlencoded
username=joaosilva&password=senhasecreta
Caso você busque mais informações em outros sites, este tipo de requisição é citada como "Simple Requests" em alguns textos.
CORS: preflight - solicitação preliminar
As requisições que não atendam às especificações acima são tratadas de forma diferenciada, como por exemplo uma requisição POST enviando um JSON.
Antes de o navegador enviar o JSON em um POST para um outro endereço que não seja o atual, ele faz uma solicitação preliminar para verificar se realmente pode enviar os dados para o destino. Essa requisição preliminar é conhecida como "preflight".
Primeiro o navegador envia uma requisição HTTP utilizando o método OPTIONS. O objetivo principal é realizar uma verificação antecipada para garantir que a requisição real seja segura através da resposta dessa requisição. Se a requisição for bem-sucedida, ou seja, o servidor responde à requisição OPTIONS com cabeçalhos CORS específicos, o navegador prossegue com o envio do JSON através de uma requisição POST.
O diagrama abaixo ilustra esse cenário:
Exemplo 2: problema de CORS envolvendo preflight
Pegando o código do primeiro exemplo, alterei o JavaScript para efetuar um POST de um JSON qualquer. Também adicionei um middleware para fazer o parse do conteúdo application/json
:
const express = require('express');
const app = express();
const port = 80;
app.get('/', (req, res) => {
res.set('Content-Type', 'text/html');
res.send(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
async function consultarDados() {
const url = 'http://api.com/dados';
// Agora enviamos um JSON através
// de uma requisição POST
const payload = {
mensagem: 'olá',
};
const response = await fetch(url, {
method: 'post',
body: JSON.stringify(payload),
headers: {
'content-type': 'application/json',
},
});
const json = await response.json();
// Renderiza o elemento na tela:
const div = document.createElement('div');
div.innerText = json.dados;
document.body.appendChild(div);
}
consultarDados();
</script>
</body>
</html>
`);
});
// Middleware para tratar o conteúdo
// do tipo application/json
app.use(express.json());
app.post('/dados', (req, res) => {
res.json({
dados: 'Dados recebidos: ' + JSON.stringify(req.body),
});
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Executando o código, podemos observar que temos um problema de CORS:
Corrigindo o problema de CORS
Por se tratar de um POST de um JSON, precisamos que quem está fornecendo o endereço (endpoint) para receber os recursos, responda à requisições com o método OPTIONS e POST fornecendo os cabeçalhos com os valores adequados para a origem da requisição. Desta forma, não teremos problemas com CORS.
Para fazer este trabalho podemos recorrer a alguma biblioteca, mas para fins didáticos vamos escrever direto no código de uma forma bem simples para ajudar no entendimento:
Repare na imagem abaixo que a requisição OPTIONS foi respondida com o status code 204 (no content) e posteriormente o POST /dados
foi efetuado com sucesso:
Tratando Múltiplas Origens no Access-Control-Allow-Origin - CORS
Em alguns casos, o dono do recurso precisa autorizar mais de uma origem a consumir seus recursos. Isso pode ser necessário, por exemplo, para permitir que uma aplicação web e um aplicativo mobile acessem o mesmo conjunto de dados.
Separar as origens por vírgula no cabeçalho Access-Control-Allow-Origin
não é a solução correta, pois isso pode gerar o erro "Multiple CORS header 'Access-Control-Allow-Origin' not allowed". A solução correta é verificar a origem da requisição e incluí-la no cabeçalho de resposta se ela estiver em uma lista de origens permitidas.
Exemplo de implementação em Node.js com Express:
app.get('/dados', (req, res) => {
const allowedOrigins = [
'http://pagina.com',
'http://outro-endereco.com',
];
const origin = req.get('origin');
if (origin && allowedOrigins.includes(origin)) {
res.set('Access-Control-Allow-Origin', origin);
}
res.set('Content-Type', 'application/json');
res.json({ dados: 'ola' });
});
Considerações
Em ambos os exemplos, ilustramos como configurar corretamente os cabeçalhos CORS para estabelecer regras sobre quem pode acessar os recursos e como fazê-lo. Por ser algo comum, normalmente os frameworks disponibilizam uma forma de efetuarmos essa configuração. Sugiro buscar na documentação do seu framework mais detalhes sobre este tema.
Em resumo, o CORS funciona através da troca de cabeçalhos HTTP entre o navegador e o servidor. Basicamente o navegador envia um cabeçalho Origin
com a origem da solicitação, e o servidor responde com cabeçalhos Access-Control-Allow-Origin
e Access-Control-Allow-Methods
indicando quais origens e métodos de solicitação são permitidos.
Deste mecanismo podemos destacar alguns pontos:
- O CORS não garante segurança completa. É crucial implementar outras medidas de segurança, como autenticação e criptografia, para proteger dados confidenciais.
- O CORS é amplamente suportado por navegadores modernos, mas é importante verificar a compatibilidade com navegadores mais antigos.
- A configuração do CORS pode ser complexa, exigindo conhecimento técnico e atenção aos detalhes.
Links interessantes: