CORS para desenvolvedores backend

Entenda os aspectos relevantes do CORS para desenvolvedores backend e também frontend

Quem nunca mandou ou ouviu um “funciona no postman / insomnia / ...” que atire a primeira pedra.

Tema de extrema importância muitas vezes desconhecido costuma se tornar uma verdadeira batata quente onde ninguém quer assumir o “b.o.” nem a solução.

Se você é desenvolvedor frontend esse artigo serve para você também 🙂

CORS - Que bicho é esse?

CORS é uma abreviação para Cross-Origin Resource Sharing. Trata-se de um mecanismo baseado em cabeçalhos do protocolo HTTP que tem como objetivo permitir a um servidor indicar origens diferentes da sua para que um browser carregue ou não determinado recurso.

Em português seria algo como Compartilhamento de Recursos de Origem Cruzada ou entre origens (distintas). Você dificilmente verá esse termo sendo usado em outros locais pois o mais comum é usar o termo técnico em inglês.

Uma requisição de origem cruzada é aquela na qual o script carregado de uma origem tenta carregar recursos de outra.

Imagine que a aplicação frontend meu-frontend.com.br tente realizar uma requisição (XMLHttpRequest e Fetch API) para minha-api.com.br/api. Temos aqui claramente um Cross-Origin Request.

Same-Origin Policy

Os browsers adotam por questões de segurança a política conhecida como Same-Origin Policy onde requisições realizadas por scripts são por padrão permitidas apenas para a mesma origem exceto se as respostas incluirem os cabeçalhos HTTP referentes ao CORS devidamente configurados.

Caso os cabeçalhos não estejam de acordo com o esperado o browser irá bloquear o carregamento do recurso desejado.

Resumo da Ópera

Por ser um mecanismo de segurança criado para browsers ferramentas como Postman, Insomnia e outras não sofrem do "problema".

Desenvolvedores backend costumam usar tais ferramentas quando precisam fazer algum teste em suas API's não obtendo portanto o erro reportado pelo desenvolvedor frontend.

Como ninguém entende o que está acontecendo vira um cabo de guerra onde o desenvolvedor frontend diz "não funciona no browser" e o desenvolvedor backend diz "funciona no postman".

Então de quem é o B.O.? De todos. Mas cabe ao desenvolvedor backend resolver uma vez que o browser checa os cabeçalhos constantes na resposta da requisição.

Como resolver o problema

O jeito mais rápido, fácil e errado é ler um tutorial na internet que lhe oriente a colocar * nos cabeçalhos envolvidos e voilà. "Tudo estará resolvido"

Há casos, como API's públicas, em que usar * está correto. No entanto há diversas ocasiões onde configurar uma lista explícita de origens válidas é o ideal e correto a ser realizado.

Cabeçalhos HTTP

Como dito no decorrer do artigo o mecanismo, implementado pelos browsers, se baseia em cabeçalhos HTTP. Portanto é necessário entender que cabeçalhos são esses e qual o papel de cada um.

Access-Control-Allow-Origin

O primeiro e mais conhecido é o Access-Control-Allow-Origin e tem como objetivo informar se determinada origem pode ou não obter determinado recurso. Esse cabeçalho deve possuir como valor * ou o mesmo valor contido no cabeçalho Origin da requisição.

Os browsers ao realizarem requisições incluem automaticamente nas mesmas o cabeçalho Origin sendo esse parte fundamental do mecanismo. O valor contido nesse cabeçalho indica a origem da requisição.

A ausência do cabeçalho Origin indica que a requisição não partiu de um browser, indicando consequentemente que não é necessário realizar validações referentes ao CORS.

Existindo o cabeçalho Origin na requisição o servidor deve responder com * caso requisições sejam aceitas de qualquer origem ou o valor contido em Origin caso o mesmo esteja contido em uma lista pré-definida, devendo tal lista ser configurada no servidor.

Vamos supor que a api minha-api.com.br/api aceite requisições de frontend-a.com.br e frontend-b.com.br mas não aceite de frontend-c.com.br. Desta forma a configuração na minha-api seria algo como:

Allowed_origins: http://frontend-a.com.br , http://frontend-b.com.br

Ao receber uma requisição que contenha o cabeçalho Origin o servidor deverá checar se o valor constante no cabeçalho está presente na lista de origens permitidas. Estando presente deverá na resposta incluir o cabeçalho Access-Control-Allow-Origin com valor igual ao do Origin. Caso o valor obtido de Origin não conste na lista de origens permitidas o servidor deverá omitir o cabeçalho Access-Control-Allow-Origin da resposta ou setar null como valor do mesmo.

Considerando o nosso exemplo teriamos que uma requisição de frontend-a.com.br para minha-api.com.br/api teria no cabeçalho de requisição Origin o valor http://frontend-a.com.br e na resposta os cabeçalhos Access-Control-Allow-Origin com valor http://frontend-a.com.br e Vary com valor Origin

# requisição
GET /api/recurso HTTP/1.1
Host: www.minha-api.com.br
Origin: http://frontend-a.com.br
...

# resposta
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://frontend-a.com.br
Vary: Origin
...

O cabeçalho Vary: Origin indica ao browser que o valor de Access-Control-Allow-Origin varia de acordo com o valor obtido de Origin

No entanto uma requisição de frontend-c.com.br para minha-api.com.br/api teria no cabeçalho de requisição Origin o valor http://frontend-c.com.br e na resposta não seria setado o cabeçalho Access-Control-Allow-Origin, gerando um erro de CORS no browser.

# requisição
GET /api/recurso HTTP/1.1
Host: www.minha-api.com.br
Origin: http://frontend-c.com.br
...

# resposta
HTTP/1.1 403 Forbidden
...

A especificação não define qual status code usar para requisições de origens não autorizadas. Usar 403 torna o erro explícito.

Caso na resposta do servidor não conste o cabeçalho Access-Control-Allow-Origin o browser exibirá no console uma mensagem de erro semelhante a abaixo:

No 'Access-Control-Allow-Origin' header is present on the requested resource

Caso o valor seja null será gerando um erro parecido com o abaixo:

Origin http://frontend-c.com.br is not allowed by 'Access-Control-Allow-Origin'

Access-Control-Allow-Methods

Há no âmbito do CORS requisições simples (simple requests) e requisições que tem como objetivo verificar (PreFlighted Requests) se uma requisição CORS pode ser realizada.

O browser é o responsável por realizar a PreFlighted Request, caso necessário, incluindo os headers apropriados. Cabe ao servidor interpretar os cabeçalhos e emitir uma resposta dentro do esperado.

Os cabeçalhos existentes numa requisição PreFlighted são Access-Control-Request-Method e Access-Control-Request-Headers. O primeiro indica que método (verbo http) será usado na requisição CORS que o browser deseja realizar enquanto que o segundo indica que cabeçalhos serão utilizados.

As respostas a essas "perguntas" são dadas respectivamente através do cabeçalhos de resposta Access-Control-Allow-Methods e Access-Control-Allow-Headers

Outra característica desse tipo de requisição é que ela deve utilizar o método HTTP OPTIONS.

Pense nessa requisição como uma solicitação de autorização. O browser antes de realizar a requisição desejada solicita autorização ao servidor para realizar uma requisição para o recurso X usando o método Y com cabeçalhos Z e W.

Se na resposta do servidor o método desejado para requisição não constar na lista exposta em Access-Control-Allow-Methods ou um cabeçalho desejado não constar na lista exposta em Access-Control-Allow-Headers a requisição não será realizada.

Parece confuso, mas não é. Imagine que o frontend-a.com.br deseje realizar uma requisição POST para minha-api.com.br/api incluindo um cabeçalho de nome App-Request-Id. Caso na resposta do servidor conste esse método e esse cabeçalho nos cabeçalhos citados o browser prosseguirá com a requisição desejada. Caso contrário a requisição não será realizada.

A requisição abaixo seria realizada com sucesso:

# requisição PreFlight
OPTIONS /api/recurso HTTP/1.1
Host: www.minha-api.com.br
Origin: http://frontend-a.com.br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: App-Request-Id
...

# resposta
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://frontend-a.com.br
Vary: Origin
Access-Control-Allow-Methods: POST, PUT
Access-Control-Allow-Headers: App-Request-Id 
...

#requisição desejada
...

Uma vez que na resposta do servidor consta o método e cabeçalho constante na PreFlight request o browser iria realizar a requisição desejada.

Caso a requisição desejada fosse para o método DELETE o browser não executaria a mesma pois tal método não consta no cabeçalho de resposta Access-Control-Allow-Methods da requisição PreFlight.

Nesse caso o browser exibiria uma mensagem parecida com a abaixo:

Method DELETE is not allowed by 'Access-Control-Allow-Methods'

Caso o problema seja no header a mensagem será parecida com a abaixo:

Header is not allowed by 'Access-Control-Allow-Headers'

Considerações finais

Há muito mais para entender sobre CORS. A especificação contém diversos detalhes que não caberia em um artigo ou o tornaria muito longo e cansativo.

Acredito que o aqui exposto permite ao leitor entender o problema e resolver a maioria dos casos.

A partir do entendimento do mecanimo adotado pelos browser o leitor pode consultar a documentação do seu framework / linguagem em busca das configurações necessárias ou implementá-las "na mão" se for do seu desejo.

Deixo abaixo dois link para quem quer se aprofundar mais no assunto: