CORS com Express
Primeiramente, você sabe o que são CORS? Já teve algum problema com ele ao longo da sua vida como desenvolvedor?
Não... então que sorte a sua 😅
Sempre quando trabalhamos com dois sistemas (internos ou não), e que precisam se comunicar entre sí, vez ou outra somos supreendidos pela seguinte mensagem do navegador:
Access to '...' at 'http://localhost...' from origin 'http://localhost...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
Mas o que vem a ser esse CORS, e por que ele é tão chato?
O que é CORS?
O CORS cujo acrônimo significa Cross-Origin Resource Sharing, é um mecanismo de segurança implementado pelos navegadores, com o objetivo de controlar a forma como os recursos de um servidor podem ser acessados por uma página web, que por sua vez, está sendo executada em um domínio diferente.
Antes do CORS, os navegadores implementavam uma política chamada Same-Origin Policy (Política de Mesma Origem), que restringia severamente as interações entre páginas de diferentes origens.
A Same-Origin Policy só permitia que uma página web interagisse com recursos de um servidor se ambos (a página e o recurso) compartilhassem a mesma origem (mesmo domínio, protocolo e porta).
Por exemplo, um site que está hospedado em https://micilini.com só poderia fazer requisições para URLs que estavam no mesmo domínio, ou seja, https://micilini.com/sites, https://micilini.com/artigos, https://micilini.com/blog, ao mesmo tempo que não poderia acessar outros sites de domínios diferentes, como: https://blog.olyng.com.
Essa política foi introduzida para evitar dois tipos comuns de ataques:
Cross-Site Scripting (XSS): ataque em que um site malicioso injeta código em outro site.
Cross-Site Request Forgery (CSRF): ataque em que o navegador é induzido a fazer requisições indesejadas a outro site em nome do usuário.
O grande problema do CORS é que ele também costuma barrar nossas requisições que são feitas dentro de ambientes locais 😅
Ou seja, se você já teve duas aplicações (front-end e back-end) configuradas no seu localhost
, mas que estavam rodando em portas diferentes, como por exemplo:
- front-end: http://localhost:3000
- back-end: http://localhost:3001
E precisasse fazer com que ambas se comunicassem, a chance de você ser barrado por uma política de CORS era gigantesca, principalmente se você estiver usando o navegador Google Chrome.
E apesar de ambas as aplicações terem sido criadas por você, o CORS não quer saber, para ele essa comunicação é INSEGURA, ele vai te barrar e pronto! 😡
Como o CORS funciona?
Quando uma determinada página tenta fazer uma requisição para um domínio diferente, por de baixo dos panos, o seu navegador envia um preflight request
(requisição de pré-verificação) por meio do método OPTIONS
para a aplicação (servidor) em questão.
Isso faz com que o servidor verifique se o destino aceita a requisição de origem cruzada.
Desse modo, o servidor responde com cabeçalhos que indicam se a origem (domínio) da requisição é permitida e quais métodos HTTP
são aceitos (como GET, POST, PUT e etc...).
Os cabeçalhos usados pelo CORS
são os seguintes:
Access-Control-Allow-Origin: especifica quais origens são permitidas. Pode ter um valor específico ou *
(para permitir qualquer origem).
Access-Control-Allow-Methods: lista os métodos HTTP
permitidos (GET, POST, etc.).
Access-Control-Allow-Headers: especifica quais cabeçalhos personalizados podem ser usados na requisição.
Access-Control-Allow-Credentials: define se credenciais como cookies ou cabeçalhos de autorização podem ser incluídos na requisição.
Vejamos abaixo um exemplo de política CORS que geralmente é definida no seu servidor (que costuma receber solicitações de outras aplicações):
Access-Control-Allow-Origin: https://localhost:3001
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
No exemplo acima, o servidor está permitindo requisições da origem https://localhost:3001
com os métodos GET
, POST
, e PUT
, e também permitindo o uso de cookies ou tokens de autenticação.
Isso significa dizer, que se você deseja solucionar o problema de CORS entre suas aplicações, basta que você informe os cabeçalhos descritos acima 🙃
Beleza, mas como eu faço isso em uma aplicação com NodeJS?
Criando nosso projeto de testes
Vamos começar criando uma nova pasta dedicada ao projeto, no meu caso, eu criei uma pasta chamada de CORS-SERVER dentro da minha área de trabalho (desktop):
Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar, precisamos inicializar o nosso projeto por meio do NPM
, sendo assim, execute o seguinte comando abaixo:
npm init -y
A flag -y
, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅
Em seguida, vamos instalar a biblioteca do express, pois iremos criar um pequeno servidor de exemplo:
npm install express
Por fim, não se esqueça de criar seu index.js
, com uma mensagem bem legal de boas vindas:
console.log('Olá Mundo!');
Feito isso, vamos subir o nosso primeiro servidor em NodeJS 😉
Configurando um servidor simples em NodeJS + Express
Para testarmos a barragem do CORS, primeiro nós iremos criar um servidor bem simples que vai rodar na porta 3000
, e que vai conter apenas uma única rota capaz de retornar uma lista de nomes em formato JSON
:
index.js
:
const express = require('express');
const app = express();
// Definir a rota que retorna a lista de nomes em JSON
app.get('/nomes', (req, res) => {
const nomes = [
{ id: 1, nome: 'Ana' },
{ id: 2, nome: 'Carlos' },
{ id: 3, nome: 'Fernanda' },
{ id: 4, nome: 'João' }
];
res.json(nomes); // Envia a lista de nomes como JSON
});
// Iniciar o servidor na porta 3000
app.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
});
Bem simples não? Após isso, não se esqueça de rodar o servidor por meio do comando:
node ./index.js
Criando nosso segundo projeto de testes
Agora vamos criar o nosso segundo projeto que vai ser responsável por enviar uma requisição para o nosso servidor que está rodando na porta 3000
.
Para isso, precisei criar uma nova pasta chamada de CLIENT-SERVER na minha área de trabalho (desktop):
Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar, precisamos inicializar o nosso projeto por meio do NPM
, sendo assim, execute o seguinte comando abaixo:
npm init -y
A flag -y
, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅
Em seguida, precisamos instalar a biblioteca do express, e do axios, pois iremos criar um pequeno servidor web, cujo o objetivo é fazer uma requisição HTTP
para o outro que está rodando na porta 3000
:
npm install express
npm install axios
Por fim, não se esqueça de criar seu index.js
, contendo uma rota do tipo GET
que faz uma requisição para o outro servidor.
const express = require('express');
const path = require('path');
const app = express();
// Rota para servir o arquivo HTML
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
// Iniciar o servidor na porta 3001
app.listen(3001, () => {
console.log('Servidor rodando na porta 3001');
});
Notou que esse servidor está rodando em uma porta diferente? Otimo... 😉
Não se esqueça de criar o index.html
na pasta raiz do seu projeto, uma vez que a nossa aplicação está carregando ele:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Requisição Fetch</title>
</head>
<body>
<h1>Testando Fetch</h1>
<button id="fetchButton">Fazer requisição Fetch</button>
<pre id="result"></pre>
<script>
document.getElementById('fetchButton').addEventListener('click', () => {
fetch('http://localhost:3000/nomes')
.then(response => response.json())
.then(data => {
document.getElementById('result').textContent = JSON.stringify(data, null, 2);
})
.catch(error => {
document.getElementById('result').textContent = 'Erro: ' + error.message;
});
});
</script>
</body>
</html>
Por fim, execute o seu servidor para rodar a aplicação:
node ./index.js
Testando o bloqueio de CORS
Com ambos os servidores rodando com o NodeJS (http://localhost:3000 e http://localhost:3001), acesse o servidor cliente (http://localhost:3001/) e clique no botão [Fazer Requisição Fetch]:
Ops... parece que um erro foi retornado 🙁
Erro: Failed to fetch
Se você abrir o console do navegador (inspecionar elemento), vai se deparar com um erro de CORS:
localhost/:1 Access to fetch at 'http://localhost:3000/nomes' from origin 'http://localhost:3001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.Understand this error
(index):15
GET http://localhost:3000/nomes net::ERR_FAILED 200 (OK)
E aí está... o CORS está bloqueando a comunicação entre nossas aplicações em NodeJS 🥳 (😖).
Esse problema aconteceu, pois estamos fazendo a nossa requisição a partir de uma página na web, usando o Fetch e o XMLHttpRequest diretamente com Javascript.
Tanto é, que se fizessemos uma requisição de forma direta (usando o axios) sem a necessidade de chamar um arquivo HTML
, como por exemplo:
const express = require('express');
const fetch = require('node-fetch'); // Importando o pacote node-fetch
const app = express();
// Rota que faz a requisição para o servidor na porta 3000
app.get('/', async (req, res) => {
try {
// Faz a requisição para o servidor na porta 3000
const response = await fetch('http://localhost:3000/nomes');
// Converte a resposta para JSON
const data = await response.json();
// Envia a resposta JSON para o cliente
res.json(data);
} catch (error) {
// Em caso de erro, envia uma resposta com erro
res.status(500).json({ error: 'Erro ao buscar nomes' });
}
});
// Inicia o servidor na porta 3001
app.listen(3001, () => {
console.log('Servidor rodando na porta 3001');
});
O resultado não daria nenhum problema de CORS, uma vez que o Axios
está blindado quanto a isso 😌
Mas voltando ao problema anterior, a forma mais sensata de resolver este problema de CORS, envolve a instalação e configuração de uma biblioteca chamada CORS, em nosso servidor que está rodando na porta 3000
.
Instalando a biblioteca CORS no servidor (CORS-SERVER)
Com o seu terminal (Prompt de Comando) aberto na pasta CORS-SERVER, execute o seguinte comando para instalar a biblioteca de cors:
npm install cors
Em seguida, dentro do seu index.js
, vamos precisar importar e implementar a biblioteca em conjunto com o express
:
const express = require('express');
const cors = require('cors'); // Importar o pacote cors
const app = express();
// Ativar o CORS para todas as rotas
app.use(cors());
// Definir a rota que retorna a lista de nomes em JSON
app.get('/nomes', (req, res) => {
const nomes = [
{ id: 1, nome: 'Ana' },
{ id: 2, nome: 'Carlos' },
{ id: 3, nome: 'Fernanda' },
{ id: 4, nome: 'João' }
];
res.json(nomes); // Envia a lista de nomes como JSON
});
// Iniciar o servidor na porta 3000
app.listen(3000, () => {
console.log('Servidor rodando na porta 3000');
});
Se você quiser limitar o CORS a apenas algumas origens específicas, você pode passar o argumento origin
em conjunto com a URL
desejada, por exemplo:
app.use(cors({
origin: 'http://localhost:3001' // Permitir apenas requisições vindas do servidor na porta 3001
}));
Por fim, basta executar o seu servidor novamente, e clicar no botão [Fazer Requisição Fetch]:
E aí está! Problema de CORS resolvido 🥳
Quais são os outros argumentos que podemos passar para a biblioteca do CORS?
Além do argumento origin
que define quais origens (domínios) podem acessar o nosso servidor (Podendo ser uma única string ou um array):
app.use(cors({
origin: 'http://example.com' // Permitir apenas um domínio específico
}));
...
// Ou uma array de domínios
app.use(cors({
origin: ['http://example.com', 'http://anotherdomain.com']
}));
...
// Ou uma função para lógica personalizada
app.use(cors({
origin: (origin, callback) => {
if (['http://example.com', 'http://anotherdomain.com'].indexOf(origin) !== -1) {
callback(null, true); // Permite a origem
} else {
callback(new Error('Não permitido por CORS')); // Bloqueia a origem
}
}
}));
Nós também podemos configurar quais métodos são permitidos:
app.use(cors({
methods: ['GET', 'POST', 'PUT', 'DELETE'] // Permitir apenas esses métodos
}));
Como também os cabeçalhos:
app.use(cors({
allowedHeaders: ['Content-Type', 'Authorization'] // Permitir esses cabeçalhos
}));
Credências:
app.use(cors({
origin: 'http://example.com',
credentials: true // Permitir o envio de credenciais
}));
Idade máxima:
app.use(cors({
maxAge: 3600 // A configuração CORS é válida por uma hora
}));
preflightContinue (se definido como true, o middleware não responderá à requisição de pré-verificação (OPTIONS), permitindo que o aplicativo a trate. Por padrão, cors responde automaticamente às requisições de pré-verificação):
app.use(cors({
preflightContinue: true // Permitir que o aplicativo trate requisições OPTIONS
}));
E optionsSuccessStatus (ele define o status HTTP a ser retornado em respostas de requisições de pré-verificação (OPTIONS). Útil para navegadores que não suportam 204):
app.use(cors({
optionsSuccessStatus: 200 // Responder com 200 ao invés de 204
}));
Vejamos abaixo um exemplo completo das configurações que podemos usar em nossas aplicações em conjunto com o CORS:
const cors = require('cors');
app.use(cors({
origin: ['http://example.com', 'http://anotherdomain.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['Content-Range', 'X-Total-Count'],
credentials: true,
maxAge: 3600,
optionsSuccessStatus: 200
}));
Arquivos da lição
Os arquivos que você viu durante o decorrer desta lição, podem ser encontrados em ambos os links abaixo:
Conclusão
Nesta lição, você aprendeu a utilizar a biblioteca CORS para permitir acessos entre aplicações.
Até a próxima lição 🥳