Erros de CORS no API Gateway: Como Configurar e o Que Seu Lambda Precisa Retornar

Você abre o console do navegador e vê aquele erro clássico: 'Access to fetch at ... has been blocked by CORS policy'. O backend responde 200 no Postman, mas o frontend quebra. Esse é um dos erros mais comuns ao integrar um frontend com API Gateway — e a causa quase sempre está em dois lugares ao mesmo tempo: a configuração do API Gateway E os headers que o Lambda retorna.

TL;DR — Resumo Rápido

CamadaO que precisa estar correto
API Gateway (REST)Habilitar CORS no recurso, criar método OPTIONS com resposta 200, configurar headers de resposta no método
API Gateway (HTTP)Configurar CORS diretamente nas configurações da API
Lambda (ambos)Retornar Access-Control-Allow-Origin e outros headers CORS em TODA resposta, incluindo erros
Erro mais comumConfigurar o API Gateway mas esquecer os headers no Lambda — o preflight passa, a requisição real falha

Como o CORS Funciona no Contexto do API Gateway

Antes de sair clicando em 'Enable CORS' no console, vale entender o que está acontecendo. O navegador, ao detectar uma requisição cross-origin, executa um preflight — uma requisição HTTP do tipo OPTIONS enviada antes da requisição real. Essa requisição OPTIONS pergunta ao servidor: 'você aceita requisições desse origin com esses métodos e headers?'

O servidor precisa responder com headers específicos autorizando a operação. Se a resposta do OPTIONS não contiver esses headers, o navegador bloqueia a requisição real antes mesmo de ela sair. Mas aqui está o ponto que engana a maioria: mesmo que o preflight passe, a requisição real também precisa retornar os headers CORS. O navegador verifica novamente.

sequenceDiagram participant B as Navegador participant AG as API Gateway participant L as Lambda B->>AG: OPTIONS /usuarios
(Preflight) AG-->>B: 200 OK
Access-Control-Allow-* Note over B: Preflight aprovado B->>AG: GET /usuarios AG->>L: Invoca Lambda L-->>AG: 200 + headers CORS AG-->>B: 200 + headers CORS Note over B: Navegador valida headers
e libera a resposta
  1. Preflight (OPTIONS): O navegador envia uma requisição OPTIONS antes da chamada real para verificar permissões CORS.
  2. Resposta do API Gateway ao OPTIONS: O API Gateway responde com os headers Access-Control-Allow-* configurados. Se ausentes, o navegador bloqueia tudo.
  3. Requisição real (GET/POST/etc): O navegador só envia a requisição real se o preflight foi aprovado.
  4. Lambda processa e responde: O Lambda precisa incluir os headers CORS na resposta — o API Gateway não os injeta automaticamente no modo de integração Lambda Proxy.
  5. Navegador valida: Se os headers CORS estiverem ausentes na resposta real, o navegador bloqueia a resposta mesmo com status 200.

Diferença Entre API Gateway REST API e HTTP API

O console tem dois tipos de API Gateway com comportamentos distintos para CORS. Confundir os dois é garantia de horas perdidas.

graph TD A["Qual tipo de API Gateway?"] --> B["REST API"] A --> C["HTTP API"] B --> D["Criar método OPTIONS
no recurso"] D --> E["Configurar headers
Access-Control-Allow-*"] E --> F["Deploy obrigatório
no Stage"] C --> G["Configurar CORS
nas Settings da API"] F --> H["Lambda retorna
headers CORS"] G --> H H --> I["CORS funcionando"]
  1. REST API: Configuração manual — você precisa criar o método OPTIONS, configurar as respostas de integração e os headers individualmente. O botão 'Enable CORS' no console automatiza parte disso, mas tem limitações.
  2. HTTP API: Configuração nativa de CORS nas configurações da API. Mais simples, mas o Lambda ainda precisa retornar os headers nas respostas reais quando se usa integração Lambda Proxy.

Configurando CORS no API Gateway REST API (Console)

O botão 'Enable CORS' no console da REST API faz o seguinte: cria um método OPTIONS no recurso selecionado, configura uma resposta mock com status 200 e adiciona os headers Access-Control-Allow-Headers, Access-Control-Allow-Methods e Access-Control-Allow-Origin à resposta do método. Depois disso, você precisa fazer o deploy da API para que as mudanças entrem em vigor.

Passo a passo via console:

  1. Acesse o API Gateway no console AWS.
  2. Selecione sua REST API.
  3. No painel de recursos, selecione o recurso desejado (ex: /usuarios).
  4. Clique em Actions → Enable CORS.
  5. Configure os campos:
    - Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
    - Access-Control-Allow-Methods: os métodos que seu recurso aceita (ex: GET,POST,OPTIONS)
    - Access-Control-Allow-Origin: o origin do seu frontend (ex: https://meusite.com) ou * para desenvolvimento
  6. Clique em Enable CORS and replace existing CORS headers.
  7. Faça o deploy da API — sem isso, nada muda. Vá em Actions → Deploy API, selecione o stage e confirme.

Via CLI, você pode verificar se o método OPTIONS existe e está configurado:

aws apigateway get-method \
  --rest-api-id SEU_API_ID \
  --resource-id SEU_RESOURCE_ID \
  --http-method OPTIONS \
  --region us-east-1

Para listar seus recursos e obter os IDs necessários:

aws apigateway get-resources \
  --rest-api-id SEU_API_ID \
  --region us-east-1

Configurando CORS no API Gateway HTTP API (Console)

A HTTP API tem uma seção dedicada de CORS nas configurações. É mais direta e não exige criação manual de método OPTIONS.

  1. Acesse sua HTTP API no console.
  2. No menu lateral, clique em CORS.
  3. Clique em Configure e preencha:
    - Access-Control-Allow-Origin: o origin do frontend
    - Access-Control-Allow-Headers: headers que o cliente enviará
    - Access-Control-Allow-Methods: métodos permitidos
    - Access-Control-Max-Age: tempo em segundos para cache do preflight
  4. Salve as configurações.

Via CLI para verificar a configuração CORS de uma HTTP API:

aws apigatewayv2 get-api \
  --api-id SEU_API_ID \
  --region us-east-1 \
  --query 'CorsConfiguration'

O Que Seu Lambda Precisa Retornar — Headers Obrigatórios

Aqui está onde a maioria erra. Quando você usa integração Lambda Proxy (que é o padrão em quase todos os tutoriais), o API Gateway repassa a resposta do Lambda diretamente para o cliente — sem modificar os headers. Isso significa que o Lambda é responsável por incluir os headers CORS em cada resposta.

Pense assim: o API Gateway configura o que responder ao preflight OPTIONS. Mas nas requisições reais, ele só faz o papel de proxy — o que o Lambda retornar é o que o cliente recebe. Se o Lambda não incluir os headers CORS, o navegador rejeita a resposta mesmo com status 200.

Estrutura mínima de resposta do Lambda com headers CORS:

🔽 Clique para expandir — Exemplo de resposta Lambda (Python)
import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Origin': 'https://meusite.com',
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
            'Content-Type': 'application/json'
        },
        'body': json.dumps({'mensagem': 'sucesso'})
    }
🔽 Clique para expandir — Exemplo de resposta Lambda (Node.js)
exports.handler = async (event) => {
    return {
        statusCode: 200,
        headers: {
            'Access-Control-Allow-Origin': 'https://meusite.com',
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ mensagem: 'sucesso' })
    };
};

Um detalhe crítico: inclua os headers CORS também nos blocos de tratamento de erro. Se o Lambda lançar uma exceção e retornar status 500 sem os headers CORS, o navegador vai bloquear a resposta de erro — e você não vai ver a mensagem de erro no frontend, só o erro de CORS.

Diagnóstico: Sintoma → Diagnóstico Errado → Causa Real → Correção

O cenário clássico: você habilita o CORS no console, faz o deploy, testa no Postman — funciona. Abre o frontend — erro de CORS. A conclusão imediata é que o API Gateway não está configurado corretamente. Você passa horas revisando o método OPTIONS, os headers de resposta, o stage de deploy.

A causa real: o Postman não envia preflight. Ele não é um navegador. Quando o Postman chama GET /usuarios, ele não verifica headers CORS — simplesmente recebe a resposta. O navegador, por outro lado, verifica os headers CORS na resposta real do GET /usuarios. Como o Lambda não estava retornando Access-Control-Allow-Origin na resposta, o navegador bloqueava.

O API Gateway estava correto. O Lambda é que não tinha os headers. A correção foi adicionar os headers na resposta do Lambda — não mexer mais no API Gateway.

Para confirmar qual camada está falhando, use o curl simulando um preflight:

curl -v -X OPTIONS https://SEU_API_ID.execute-api.us-east-1.amazonaws.com/prod/usuarios \
  -H 'Origin: https://meusite.com' \
  -H 'Access-Control-Request-Method: GET' \
  -H 'Access-Control-Request-Headers: Content-Type'

E depois simule a requisição real:

curl -v -X GET https://SEU_API_ID.execute-api.us-east-1.amazonaws.com/prod/usuarios \
  -H 'Origin: https://meusite.com' \
  -H 'Content-Type: application/json'

Se o OPTIONS retorna os headers mas o GET não retorna Access-Control-Allow-Origin, o problema está no Lambda — não no API Gateway.

Permissões IAM Necessárias

Para executar os comandos CLI de diagnóstico e configuração, a role ou usuário precisa das seguintes permissões mínimas:

🔽 Clique para expandir — Política IAM mínima para diagnóstico CORS
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "apigateway:GET"
      ],
      "Resource": [
        "arn:aws:apigateway:us-east-1::/restapis/*",
        "arn:aws:apigateway:us-east-1::/apis/*"
      ]
    }
  ]
}

Para modificar configurações de CORS via CLI ou console, adicione apigateway:PUT e apigateway:PATCH ao escopo acima. Siga o princípio de menor privilégio — em produção, restrinja ao ARN específico da API.

Checklist de Verificação CORS no API Gateway

  1. REST API: O método OPTIONS existe no recurso? A resposta do método tem os três headers Access-Control-Allow-*? O deploy foi feito após as mudanças?
  2. HTTP API: A configuração CORS está salva nas configurações da API? O origin está correto (sem barra no final)?
  3. Lambda: Todos os handlers de resposta — incluindo erros — retornam Access-Control-Allow-Origin?
  4. Origin: O valor de Access-Control-Allow-Origin corresponde exatamente ao origin do frontend? https://meusite.com e https://meusite.com/ são diferentes para alguns navegadores.
  5. Credenciais: Se o frontend envia cookies ou headers de autenticação, Access-Control-Allow-Credentials: true é necessário — e nesse caso Access-Control-Allow-Origin não pode ser *.

Próximos Passos e Recursos Oficiais

Com CORS configurado corretamente nos dois lados — API Gateway e Lambda — o erro de bloqueio cross-origin desaparece. O ponto de atenção contínuo é garantir que qualquer novo endpoint ou novo handler de erro no Lambda também inclua os headers. Uma abordagem comum em produção é centralizar a construção da resposta em uma função utilitária que sempre injeta os headers CORS, evitando que um novo desenvolvedor esqueça de incluí-los.

Glossário de Termos-Chave

TermoDefinição
CORS (Cross-Origin Resource Sharing)Mecanismo do navegador que controla quais origens podem acessar recursos de um domínio diferente via requisições HTTP.
Preflight RequestRequisição OPTIONS enviada automaticamente pelo navegador antes de requisições cross-origin para verificar permissões do servidor.
Lambda Proxy IntegrationModo de integração onde o API Gateway repassa o evento completo ao Lambda e retorna a resposta do Lambda diretamente ao cliente, sem modificações.
Access-Control-Allow-OriginHeader HTTP que especifica quais origens têm permissão para acessar o recurso. Obrigatório em toda resposta CORS.
Stage (API Gateway)Ambiente de deploy de uma API REST (ex: prod, dev). Mudanças na configuração só têm efeito após um novo deploy no stage.

Comentários

Postagens mais visitadas deste blog

Variáveis de Ambiente no Lambda: Configuração, Acesso e Criptografia com KMS

Monitoramento de Memória RAM no EC2: Por que o CloudWatch Agent é Obrigatório

S3 Access Denied: Por que 'Bloquear Acesso Público' impede seu objeto mesmo após torná-lo público