CORS em APIs
Nesta aula, você aprenderá sobre CORS (Cross-Origin Resource Sharing) em APIs PHP, desde o conceito de same-origin até a implementação prática dos headers, incluindo o tratamento de requisições preflight e a configuração adequada do servidor.
Quando você desenvolve uma API em PHP, uma das questões mais comuns — e frequentemente frustrantes — é o erro de CORS no navegador. Mensagens como "Access to fetch at 'https://api.exemplo.com' from origin 'http://localhost:3000' has been blocked by CORS policy" são familiares a qualquer desenvolvedor que já tenha integrado um front-end separado de um back-end. Nesta aula, vamos desmistificar o CORS: o que é, por que ele existe, como funciona na prática e, principalmente, como configurá-lo corretamente em suas APIs PHP.
O CORS não é uma falha de segurança, mas sim um mecanismo de proteção implementado pelos navegadores. Ele controla quais origens (domínios) podem acessar recursos de outras origens. Compreender os conceitos de same-origin, headers, preflight e configuração permitirá que você construa APIs seguras e acessíveis para os clientes certos.
Same-origin
O conceito de same-origin (mesma origem) é fundamental para entender o CORS. Duas URLs têm a mesma origem se compartilham o mesmo protocolo (http, https), domínio (exemplo.com) e porta (80, 443, 3000). Por exemplo:
http://exemplo.com/paginaehttp://exemplo.com/outra— mesma origem (mesmo protocolo, domínio e porta padrão 80).http://exemplo.comehttps://exemplo.com— origens diferentes (protocolo diferente).http://exemplo.comehttp://www.exemplo.com— origens diferentes (subdomínio diferente).http://exemplo.com:3000ehttp://exemplo.com:8080— origens diferentes (porta diferente).
O navegador aplica a Política de Mesma Origem (Same-Origin Policy), que restringe scripts de uma origem de acessar dados de outra origem. Isso impede, por exemplo, que um site malicioso faça requisições para o seu banco ou para serviços autenticados sem permissão. No entanto, essa política também bloqueia requisições legítimas entre front-end e back-end em domínios diferentes — é aí que o CORS entra como uma exceção controlada.
Quando você faz uma requisição fetch ou XMLHttpRequest de uma origem para outra, o navegador envia a requisição e verifica se a resposta inclui os headers de CORS adequados. Se não, o navegador bloqueia o acesso ao código JavaScript à resposta. É importante notar que a requisição pode até chegar ao servidor, mas o navegador impede que o JavaScript leia a resposta.
Headers de CORS
Os headers de CORS são enviados pelo servidor na resposta HTTP para indicar quais origens são permitidas. Os principais são:
Access-Control-Allow-Origin: especifica quais origens podem acessar o recurso. Pode ser um domínio específico (https://meusite.com) ou*(qualquer origem, mas não funciona com credenciais).Access-Control-Allow-Methods: lista os métodos HTTP permitidos (GET, POST, PUT, DELETE, etc.).Access-Control-Allow-Headers: lista os headers que podem ser enviados na requisição (por exemplo,Content-Type,Authorization).Access-Control-Allow-Credentials: indica se cookies e credenciais são permitidos. Deve sertruese você usar autenticação baseada em cookies.Access-Control-Expose-Headers: quais headers da resposta o JavaScript pode acessar (por exemplo,X-Total-Count).Access-Control-Max-Age: por quanto tempo (em segundos) o navegador pode cachear a resposta preflight.
No PHP, você pode enviar esses headers usando a função header(). Por exemplo, para permitir apenas uma origem específica:
header('Access-Control-Allow-Origin: https://meusite.com');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
Se você quiser permitir qualquer origem (comum para APIs públicas), pode usar *, mas lembre-se de que não funcionará com credenciais (cookies).
header('Access-Control-Allow-Origin: *');
É importante definir esses headers no início do script, antes de qualquer saída. Em frameworks como Laravel, você pode usar middlewares para centralizar essa lógica.
Preflight
Para requisições que não são consideradas "simples", o navegador envia uma requisição preflight (voo preliminar) antes da requisição real. Uma requisição é considerada simples se:
- Usa apenas métodos GET, HEAD ou POST;
- Com POST, o Content-Type é um dos seguintes:
application/x-www-form-urlencoded,multipart/form-dataoutext/plain; - Não usa headers customizados (apenas os considerados seguros, como Accept, Accept-Language, Content-Language).
Se a sua requisição usa, por exemplo, Content-Type: application/json, ou um header de autorização, ou o método PUT/DELETE, o navegador fará uma requisição OPTIONS para o servidor antes da requisição real. O preflight pede permissão para a requisição real, e o servidor deve responder com os headers de CORS adequados, mas sem retornar o recurso.
No PHP, você precisa tratar o método OPTIONS explicitamente. Um exemplo simples:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400'); // 24 horas
http_response_code(204);
exit;
}
Se você não responder ao preflight, o navegador bloqueia a requisição real. Portanto, é essencial incluir esse tratamento em qualquer API que receba requisições cross-origin.
Configuração
Vamos construir um exemplo completo de uma API simples em PHP com CORS configurado dinamicamente. Vamos permitir apenas origens de uma lista permitida, o que é mais seguro do que *.
// config.php
$allowedOrigins = [
'https://meusite.com',
'http://localhost:3000'
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400');
}
// Trata preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
Em um arquivo api.php, você pode incluir essa configuração e então processar a requisição normalmente:
require 'config.php';
// Roteamento simples
$method = $_SERVER['REQUEST_METHOD'];
$path = $_GET['path'] ?? '/';
if ($method === 'GET' && $path === '/dados') {
header('Content-Type: application/json');
echo json_encode(['mensagem' => 'Olá, CORS!']);
} else {
http_response_code(404);
echo json_encode(['erro' => 'Rota não encontrada']);
}
Se você usa um framework como Laravel, pode criar um middleware para centralizar as configurações de CORS. Por exemplo:
namespace App\Http\Middleware;
use Closure;
class Cors
{
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
}
}
Lembre-se de registrar o middleware no kernel do Laravel e, se necessário, tratar o preflight no roteamento (ou usar o pacote fruitcake/laravel-cors).
Boas práticas
- Não use
*com credenciais: se sua API usa cookies ou autenticação HTTP, você deve especificar origens exatas e definirAccess-Control-Allow-Credentials: true. - Valide a origem no servidor: além de devolver o header, você pode rejeitar requisições de origens não autorizadas (retornando 403).
- Cacheie preflight: use
Access-Control-Max-Agepara reduzir o número de requisições OPTIONS. - Separe configuração por ambiente: em desenvolvimento, permita
http://localhost:3000; em produção, apenas seu domínio real. - Use HTTPS: nunca envie headers de CORS em conexões inseguras.
Referências
- MDN: CORS
- MDN: Same-origin policy
- MDN: Access-Control-Allow-Origin
- MDN: OPTIONS
- PHP: header()
- Laravel: CORS
- fruitcake/laravel-cors
Exercícios
- Explique o que é same-origin e dê dois exemplos de URLs que têm origens diferentes.
- Quais são os principais headers de CORS e para que serve cada um?
- O que é uma requisição preflight e quando ela ocorre?
- Escreva um código PHP que configure CORS para permitir apenas as origens
https://site1.comehttps://site2.com, e que responda corretamente ao preflight. - Em uma API PHP, você precisa permitir que o front-end em
http://localhost:3000envie requisições com o headerAuthorizatione o métodoDELETE. Quais headers você deve enviar e como tratar o preflight?
http://exemplo.com e https://exemplo.com (protocolo diferente); http://exemplo.com e http://api.exemplo.com (subdomínio diferente).Access-Control-Allow-Origin (define origens permitidas), Access-Control-Allow-Methods (métodos HTTP permitidos), Access-Control-Allow-Headers (headers permitidos na requisição), Access-Control-Allow-Credentials (permite credenciais), Access-Control-Expose-Headers (headers que o JS pode ler) e Access-Control-Max-Age (cache de preflight).$allowedOrigins = ['https://site1.com', 'https://site2.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400');
}
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}Access-Control-Allow-Origin: http://localhost:3000, Access-Control-Allow-Methods: DELETE, OPTIONS (ou incluir outros), Access-Control-Allow-Headers: Authorization. Além disso, tratar o método OPTIONS respondendo 204 com esses headers.Observações finais
CORS é um tópico que parece simples, mas exige atenção aos detalhes. Sempre teste suas configurações em diferentes navegadores e cenários. Use as ferramentas de desenvolvedor do navegador para inspecionar as requisições e verificar se os headers estão corretos. Lembre-se de que CORS é uma proteção do navegador, não do servidor; a segurança real deve ser implementada no servidor com autenticação e autorização adequadas.