AWS Serverless - API Gateway e CORS

Seu guia de sobrevivência sobre CORS e Serverless APIs

Image for post
Image for post
Créditos da Imagem

A criação de APIs para web é um dos casos de uso mais populares para aplicativos serverless. Você obtém o benefício de um back-end simples e escalável sem a sobrecarga de operações.

No entanto, se você tiver uma página web que esteja fazendo chamadas para uma API, você terá que lidar com Cross-Origin Resource Sharing, ou CORS. Se sua página web faz uma solicitação HTTP para um domínio diferente do que você está atualmente, ela precisa ser compatível com o CORS.

Se você já se encontrou com o seguinte erro:

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

Então você está no lugar certo!

Neste artigo, abordaremos tudo o que você precisa saber sobre Serverless + CORS. Se você não se importa com os detalhes, veja a seção TL; DR abaixo. Caso contrário, abordaremos:

  • Solicitações de Comprovação (Preflight Requests)
  • Cabeçalhos de Resposta (Response Headers)
  • CORS com Autorizações Personalizadas
  • CORS com Credenciais de Cookie (Cookie Credentials)

Vamos começar!

TL; DR

Se você quiser a maneira rápida e suja de resolver o CORS em seu aplicativo serverless, faça o seguinte:

  1. Para lidar com solicitações preflight, adicione o cors: true a cada endpoint HTTP em seu serverless.yml:
# serverless.yml

service: products-service

provider:
name: aws
runtime: nodejs6.10

functions:
getProduct:
handler: handler.getProduct
events:
- http:
path: product/{id}
method: get
cors: true # <-- CORS!
createProduct:
handler: handler.createProduct
events:
- http:
path: product
method: post
cors: true # <-- CORS!
  1. Para lidar com o cabeçalho de resposta do CORS, você precisa retornar os cabeçalhos CORS em sua resposta. Os cabeçalhos principais são Access-Control-Allow-Origin e Access-Control-Allow-Credentials.

Você pode usar o exemplo abaixo ou confira as bibliotecas de middleware discutidas no artigo para ajudar com isso:

'use strict';

module.exports.getProduct = (event, context, callback) => {

// Busca um produto
const product = retrieveProduct(event);

const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};

module.exports.createProduct = (event, context, callback) => {

// Cria um novo produto
const product = createProduct(event);

const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};
  1. Se você estiver usando um autorizador personalizado, será necessário adicionar o seguinte CloudFormation ao seu bloco resources do serverless.yml:
# serverless.yml

...

resources:
Resources:
GatewayResponseDefault4XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers:"'*'"
ResponseType: DEFAULT_4XX
RestApiId:
Ref: 'ApiGatewayRestApi'

Solicitações de Comprovação (Preflight Requests)

Se você não estiver fazendo uma “requisição simples”, seu navegador enviará um preflight request usando o método OPTIONS. O recurso que você está solicitando retornará os métodos seguros que podem ser enviados para a API e pode, opcionalmente, retornar os cabeçalhos válidos para o envio.

Vamos acabar com isso.

Quando que meu navegador envia um ‌Preflight Request?

Seu navegador enviará um preflight request em quase todas as requisições de origem cruzada (cross-origin requests). As exceções são “solicitações simples”, porém, são um subconjunto bastante restrito.

Basicamente, “uma solicitação simples” é apenas uma solicitação GET ou uma solicitação POST com dados de formulário sem autenticação. Se você estiver fora desse formato, o navegador enviará um preflight request.

Se você usar um pedido PUT ou DELETE, ele enviará uma preflight request. Se você usar um cabeçalho Content-Type fora de uma requisição application/x-www-form-urlencoded, multipart/form-data ou text/plain, ele irá enviar um preflight request. Se você incluir qualquer cabeçalho diferente de alguns muito básicos, como cabeçalhos de autenticação (Authentication), ele enviará um preflight request.

O que há na resposta dos ‌Preflight Request?

A resposta a um preflight request inclui os domínios permitidos à acessar os recursos e seus métodos, tais como GET, POST, PUT, etc. A resposta também pode incluir os cabeçalhos que são permitidos na comunicação, tais como Authentication.

Como faço para lidar com solicitações de preflight com o Serverless?

Para configurar respostas para um ‌preflight request, você precisará ter um manipulador para cuidar do método OPTIONS em seu API Gateway. Felizmente, isso é muito simples com o Serverless Framework.

Basta adicionar cors: true a cada endpoint em seu serverless.yml:

# serverless.yml

service: products-service

provider:
name: aws
runtime: nodejs6.10

functions:
getProduct:
handler: handler.getProduct
events:
- http:
path: product/{id}
method: get
cors: true # <-- CORS!
createProduct:
handler: handler.createProduct
events:
- http:
path: product
method: post
cors: true # <-- CORS!

Isso configura o API Gateway para permitir que qualquer domínio tenho acesso e também inclua o conjunto básico de cabeçalhos permitidos. Se você quiser uma personalização adicional (de uso avançado), ficará assim:

# serverless.yml

service: products-service

provider:
name: aws
runtime: nodejs6.10

functions:
getProduct:
handler: handler.getProduct
events:
- http:
path: product/{id}
method: get
cors:
origin: '*' # <-- Origens permitidas
headers: # <-- Cabeçalhos customizados
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
allowCredentials: false

Cabeçalhos de Resposta CORS

Embora preflight requests se aplique somente a algumas requisições cross-origin, os cabeçalhos de resposta do CORS devem estar presentes em todas as solicitações cross-origin. Isso significa que você deve adicionar o cabeçalho Access-Control-Allow-Origin às suas respostas em seus manipuladores.

Se você estiver usando cookies ou outro tipo de autenticação, você também precisará adicionar o cabeçalho Access-Control-Allow-Credentials à sua resposta.

Usando o serverless.yml da seção acima, seu arquivo handler.js deve se parecer com:

// handler.js

'use strict';

module.exports.getProduct = (event, context, callback) => {

// Busca um produto
const product = retrieveProduct(event);

const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};

module.exports.createProduct = (event, context, callback) => {

// Cria um novo produto
const product = createProduct(event);

const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};

Observe como o objeto response tem uma propriedade headers, que contém um objeto com Access-Control-Allow-Origin e Access-Control-Allow-Credentials.

Pode ser difícil adicionar esses cabeçalhos em todas as suas funções, especialmente se você tiver vários caminhos em sua lógica. Felizmente, existem algumas boas ferramentas para nos ajudar com isso!

Se você usar JavaScript, confira a mecânica do middleware Middy para uso com Lambdas. Ele fornece vários middlewares de uso agradável, ​​que lidam com o clichês chatos de funções Lambda. Um é o middleware de cors, que adiciona automaticamente os cabeçalhos CORS às suas funções.

Um exemplo básico é assim:

// handler.js

const middy = require('middy')
const { cors } = require('middy/middlewares')

// Esse é o seu manipulador comum, não é diferente do que você está acostumado a fazer todos os dias
// no AWS Lambda
const hello = (event, context, callback) => {

const response = {
statusCode: 200,
body: "Hello, world!"
}

return callback(null, response)
}

// Vamos "middyfy" nosso manipulador, então poderemos conectar middlewares a ele
const handler = middy(hello)
.use(cors()) // Adiciona cabeçalhos CORS às respostas

module.exports = { handler }

Perfeito — cabeçalhos automáticos para CORS! Confira toda a biblioteca Middy para muitos outros utilitários maneiros.

Se você é um Pythonista, Daniel Schep fez uma boa biblioteca chamada lambda-decorators, com os mesmos objetivos de Middy — substituir os clichês das funções Lambda.

Aqui está um exemplo em suas funções do Python:

# handler.py

from lambda_decorators import cors_headers

@cors_headers
def hello(event, context):
return {
'statusCode': 200,
'body': "Hello, world!"
}

Nota: Daniel é o criador do pacote serverless-python-requirements, o qual você deve absolutamente usar se estiver escrevendo funções Lambda em Python. Confira nossa postagem anterior no blog sobre o pacote Python.

CORS com Autorizações Personalizadas

Autorizadores personalizados permitem que você proteja seus endpoints Lambda com uma função que é responsável pelo tratamento da autorização.

Se a autorização for bem sucedida, ela encaminhará a solicitação para o manipulador da Lambda. Se não der certo, rejeitará a solicitação e retornará ao usuário.

A dificuldade com CORS está no segundo cenário — se você rejeitar uma solicitação de autorização, você não poderá especificar os cabeçalhos CORS na sua resposta. Isso pode dificultar a vida do navegador do cliente para entender a resposta.

Para lidar com isso, você precisará adicionar uma GatewayResponse personalizada a sua configuração da API Gateway. Você adicionará isso no bloco resources do seu serverless.yml:

functions:
...

resources:
Resources:
GatewayResponseDefault4XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'*'"
gatewayresponse.header.Access-Control-Allow-Headers: "'*'"
ResponseType: DEFAULT_4XX
RestApiId:
Ref: 'ApiGatewayRestApi'

Isso garantirá que os cabeçalhos de resposta apropriados sejam retornados de seu autorizador personalizado assim que reijeitar uma solicitação de autorização.

CORS com Credenciais de Cookie

Nota: Esta seção foi adicionada em 29 de janeiro de 2018 graças a um pedido de Alex Rudenko. Dica ótima de Martin Splitt que tem um artigo sobre esta questão.

Nos exemplos acima, fornecemos um caractere curinga "*" como o valor do cabeçalho Access-Control-Allow-Origin. No entanto, se você estiver fazendo uma requisição usando credenciais, o valor curinga não será permitido. Para que seu navegador utilize a resposta, os cabeçalhos de resposta Access-Control-Allow-Origin devem incluir a origem específica que realizou a solicitação.

Existem duas maneiras de lidar com isso. Primeiro, se você tiver apenas um site de origem que está fazendo a solicitação, basta codificar isso na resposta da função do Lambda:

// handler.js

'use strict';

module.exports.getProduct = (event, context, callback) => {

// Busca um produto
const product = retrieveProduct(event);

const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': 'https://minha-origem.com.br', // <-- Adicione sua origem aqui
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};

Se você tiver vários endereços de origem que podem estar utilizando sua API, será necessário fazer uma abordagem mais dinâmica. Você pode inspecionar o cabeçalho origin para ver se está na sua lista de origens aprovadas. Se estiver, retorne o valor de origem no seu cabeçalho Access-Control-Allow-Origin:

// handler.js

'use strict';

const ALLOWED_ORIGINS = [
'https://primeira-origem.com',
'https://segunda-origem.com',
];

module.exports.getProduct = (event, context, callback) => {

const origin = event.headers.origin;
let headers;

if (ALLOWED_ORIGINS.includes(origin) {
headers: {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Credentials': true,
},
} else {
headers: {
'Access-Control-Allow-Origin': '*',
},
}

// Busca um produto
const product = retrieveProduct(event);

const response = {
statusCode: 200,
headers
body: JSON.stringify({
product: product
}),
};

callback(null, response);
};

Neste exemplo, verificamos se o cabeçalho origin corresponde a um de nossos domínios permitidos. Se for verdadeiro, nós incluímos a origem em nosso cabeçalho Access-Control-Allow-Origin e também dizemos que Access-Control-Allow-Credentials é permitido. Se a origin não corresponder a nenhum item da lista de permissão, nós incluímos os cabeçalhos padrões, rejeitando a requisição se essa origem tentar uma requisição credenciada.

Conclusão

CORS pode ser uma dor de cabeça, mas existem alguns passos simples que você pode tomar para tornar tudo muito mais fácil de lidar.

Agora você já sabe como sobreviver. Diga adeus ao inexplicável erro No 'Access-Control-Allow-Origin' header is present on the requested resource. 👋👋👋

Créditos ⭐️

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store