Menu

CORS no Express: resolver o erro de CORS (com e sem cors)

Um erro de CORS significa que o navegador impediu uma página de ler a resposta da sua API porque a resposta não permitia a origem da página. Resolva no servidor enviando Access-Control-Allow-Origin e respondendo as requisições de preflight OPTIONS, na mão ou com o pacote cors.

Esta página tem editores executáveis. Edite, execute e veja a saída na hora.

Um erro de CORS ("No 'Access-Control-Allow-Origin' header is present") significa que uma página web em uma origem, como http://localhost:5173, chamou sua API em outra origem, e o navegador não deixou a página ler a resposta porque a resposta não dizia que aquela origem era permitida. A correção é no servidor: envie Access-Control-Allow-Origin com a origem da página, e responda a requisição de preflight OPTIONS do navegador com os métodos e headers que você aceita. No Express isso são poucas linhas de middleware, ou app.use(cors()) do pacote cors.

A resposta é a lista de produtos, e a aba Headers mostra access-control-allow-origin: https://app.example.com: uma página nessa origem pode lê-la. Agora envie OPTIONS /api/products/1 com os headers Origin: https://app.example.com e Access-Control-Request-Method: PUT. Esse é o preflight que um navegador envia antes de um PUT, e a resposta é 204 com os métodos e headers permitidos e sem body.

O que o navegador confere

Páginas de mesma origem não precisam de nada. Para uma requisição entre origens, o navegador lê estes headers da resposta:

HeaderSignificado
Access-Control-Allow-OriginA única origem que pode ler a resposta, ou * para qualquer uma
Access-Control-Allow-MethodsMétodos permitidos na requisição real (só no preflight)
Access-Control-Allow-HeadersHeaders que a página pode enviar, como Authorization (só no preflight)
Access-Control-Allow-Credentialstrue se a página pode enviar cookies e ler a resposta
Access-Control-Max-AgeSegundos que o navegador pode guardar a resposta do preflight em cache
Vary: OriginAvisa os caches que a resposta depende do header Origin da requisição

Um GET simples ou um POST no estilo de formulário vai direto para o servidor, e o navegador confere Access-Control-Allow-Origin na resposta. O servidor executa a requisição mesmo assim; o CORS só decide se a página vê o resultado. Uma requisição com body JSON, com header Authorization ou com um método como PUT ou DELETE é precedida por uma requisição de preflight OPTIONS, e a requisição real só é enviada se a resposta do preflight permitir. Por isso um middleware de CORS precisa responder o OPTIONS antes de qualquer rota ou verificação de autenticação rodar.

Liberar várias origens, com credentials

Access-Control-Allow-Origin guarda uma origem ou *, nunca uma lista. Para liberar várias, compare o header Origin da requisição com uma lista de permitidas e devolva o mesmo valor quando ele estiver nela:

A primeira requisição não tem header Origin, como uma chamada do curl ou de uma página de mesma origem, então ela recebe {"origin":null} e nenhum header de CORS. Envie GET /api/me com o header Origin: http://localhost:5173 e a aba Headers mostra essa origem devolvida mais access-control-allow-credentials: true. Troque o header para Origin: https://evil.example e os dois headers de CORS somem: o servidor ainda responde, mas um navegador impediria a página de ler. Access-Control-Allow-Credentials: true deixa a página enviar cookies (com fetch(url, { credentials: 'include' })), e os navegadores o rejeitam junto com *, então credentials sempre exige a origem exata.

O pacote cors

O pacote cors faz o mesmo trabalho com opções. Instale com npm install cors:

const cors = require('cors');

// Any origin, no cookies: fine for a public read-only API.
app.use(cors());

// Only your frontend, with cookies.
app.use(cors({
    origin: ['https://app.example.com', 'http://localhost:5173'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));

// One route only.
app.get('/api/public', cors(), handler);

Chamado sem opções, cors() envia Access-Control-Allow-Origin: * e responde todo preflight. Use * só para dados públicos que não exigem login.

Erros comuns

Adicionar CORS depois das rotas ou depois de um middleware de autenticação. A requisição de preflight OPTIONS não leva header Authorization, então uma verificação de autenticação colocada antes responde com 401 e o navegador informa um erro de CORS. Registre o middleware de CORS primeiro.

Usar * com credentials. Os navegadores recusam a resposta. Devolva a origem específica.

Tentar resolver CORS no frontend. Opções de requisição como mode: 'no-cors' não removem a restrição; elas te dão uma resposta opaca que você não consegue ler. Só os headers do servidor resolvem.

Para entender como funcionam o app.use() e a ordem dos middlewares, veja middleware no Express.

Perguntas frequentes

Por que minha API funciona no Postman ou no curl, mas não no navegador?

O CORS só é aplicado pelos navegadores. Postman, curl e outros servidores ignoram os headers de CORS, então leem a resposta de qualquer jeito. O navegador se recusa a entregar a resposta para scripts de uma página de outra origem, a menos que o servidor permita essa origem.

CORS é um recurso de segurança do meu servidor?

Ele protege os usuários, não o seu servidor. Ele impede que uma página maliciosa leia respostas da sua API com os cookies do usuário. Qualquer um ainda pode chamar sua API a partir de um script ou de um servidor, então autenticação e autorização continuam sendo trabalho seu.

O que conta como outra origem?

Uma origem é esquema, host e porta juntos. http://localhost:5173 e http://localhost:3000 são origens diferentes porque as portas mudam, e https://example.com e https://api.example.com mudam no host.

Dá para evitar CORS em desenvolvimento?

Sim, tornando a requisição de mesma origem: configure o servidor de desenvolvimento do frontend (o server.proxy do Vite, por exemplo) para encaminhar /api para o servidor Express. O navegador então fala com uma única origem e nenhum header de CORS é necessário.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

Começar