AWS Serverless - API Gateway e CORS
Tags: ptbr, aws, serverless, webdev
Published: 2019-06-09 23:07:44

## O seu guia de sobrevivência sobre Serverless APIs

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`:

```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!
```

2. 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:

```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);
};
```

3. Se você estiver usando um autorizador personalizado, será necessário adicionar o seguinte **CloudFormation** ao seu bloco `resources` do `serverless.yml`:

```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**](https://github.com/serverless/serverless).

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

```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:

```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:
            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:

```js
// 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](https://middy.js.org) 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:

```js
// 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](https://twitter.com/schep_) fez uma boa biblioteca chamada [lambda-decorators](https://github.com/dschep/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:

```py
# 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](https://github.com/UnitedIncome/serverless-python-requirements), o qual você deve absolutamente usar se estiver escrevendo funções Lambda em Python. Confira nossa [postagem](https://serverless.com/blog/serverless-python-packaging/) anterior no [blog sobre o pacote Python](https://serverless.com/blog/serverless-python-packaging/).

# CORS com Autorizações Personalizadas

[Autorizadores personalizados](https://serverless.com/framework/docs/providers/aws/events/apigateway/#http-endpoints-with-custom-authorizers) 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`:

```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](https://twitter.com/orKoN). Dica ótima de Martin Splitt que tem um [artigo sobre esta questão](http://50linesofco.de/post/2017-03-06-cors-a-guided-tour#credentials-and-cors).

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](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Requests_with_credentials), 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:

```js
// 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`:

```js
// 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 ⭐️
- [Your CORS and API Gateway survival guide](https://serverless.com/blog/cors-api-gateway-survival-guide/), escrito originalmente por [Alex DeBrie](https://twitter.com/alexbdebrie)