serverless: custom response parameter mappings in HttpApi - amazon-web-services

I am trying to send a custom Response header from my API, I tried using events.response.statusCodes but it is not working, looks like it was only implemented for http but not for httpApi event.
functions:
myfunction:
name: test
handler: src/index.handler
events:
- httpApi:
path: /graphql
method: post
response:
statusCodes:
200:
headers:
Strict-Transport-Security: "'max-age=31536000'"
500:
headers:
Strict-Transport-Security: "'max-age=31536000'"

you are corrrect that it hasn't been implemented for httpApi. It is supported by HTTP API on AWS level though, so you can override manually properties of created AWS::ApiGatewayV2::Integration by using the following syntax:
resources:
extensions:
<LogicalIdOfYourIntegrationResource>:
Properties:
ResponseParameters: ...
See CloudFormation docs for that resource for reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-integration.html#cfn-apigatewayv2-integration-responseparameters

Related

AWS Api Gateway does not respond with CORS headers only on OPTIONS call

I tried to add CORS to my HTTP API and it does work for GET, POST, etc. but not for OPTIONS calls. What could be the reason?
It is a completely new HTTP API in AWS API Gateway. I added some hello world lambda function as a route and * as an allowed origin. I thought the whole point of OPTIONS calls is to send these headers...
Here is Postman requesting POST
And here with OPTIONS
This is my routes config
And this is my CORS config
You should be able to easily add OPTIONS support for your API in your specification:
options:
summary: CORS support
description: |
Enable CORS by returning correct headers
tags:
- CORS
responses:
200:
description: Default response for CORS method
headers:
Access-Control-Allow-Origin:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Headers:
schema:
type: string
content: {}
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{
"statusCode" : 200
}
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Origin: "'*'"
responseTemplates:
application/json: |
{}
This should work for non proxy integrations and returns the correct headers directly from the API and not from the lambda. You may need to modify the response parameters to suit your needs.
For proxy integrations, you need to implement the response in the lambda function (https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html)

How to configure CORS for AWS API Gateway using OpenAPI?

I have an OpenAPI spec for an api which I am deploying via CDK. The spec looks like:
openapi: 3.0.1
info:
title: My API
description: My REST API with CORS enabled
version: 0.1.0
x-amazon-apigateway-cors:
allowOrigins:
- "*"
allowCredentials: true
exposeHeaders:
- "x-apigateway-header"
- "x-amz-date"
- "content-type"
maxAge: 3600
allowMethods:
- "*"
allowHeaders":
- "x-apigateway-header"
- "x-amz-date"
- "content-type"
- "Authorization"
components:
securitySchemes:
lambda:
type: "apiKey"
name: "Authorization"
in: "header"
x-amazon-apigateway-authtype: "custom"
x-amazon-apigateway-authorizer:
authorizerUri: "{{my-lambda-authorizer}}"
authorizerResultTtlInSeconds: 300
type: "token"
paths:
/user/{id}:
get:
summary: Get info of specified user.
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/User'
security:
- lambda: []
x-amazon-apigateway-integration:
uri: "{{my-lambda}}"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
type: "aws_proxy"
When I try to access this via fetch(), I get an error Failed to load resource: Origin http://localhost:8000 is not allowed by Access-Control-Allow-Origin.
fetch('https://api.example.com/user/1')
.then(response => response.json())
.then((user: User) => {
// do something
})
.catch((err) => {
console.log("Error: " + err);
});
The API is accessible at api.example.com and I am running the website locally using Gatsby at localhost:8000.
The AWS docs seem to state that CORS is enabled when I put x-amazon-apigateway-cors at the root of the spec, but CORS doesn't seem to be enabled (or working) when I try to access the API. How do I enable CORS for my API without needing to configure it in the console?
Amazon API Gateway offers two types of APIs: REST APIs and HTTP APIs. REST APIs were the kind of APIs originally introduced with Amazon API Gateway, while HTTP APIs got announced at the end of 2019.
Based on the description of your OpenAPI specification I assume you're trying to deploy a REST API. The x-amazon-apigateway-cors OpenAPI extension however only works for HTTP APIs.
You have two choices now: You either switch to use a HTTP API or you configure CORS manually. As long as you don't need features only supported by REST APIs, I suggest you switch to use a HTTP API, as that's the more modern kind of API Amazon API Gateway offers.
If you want to use a REST API, enabling CORS requires more manual configuration. That's documented by AWS in Enabling CORS for a REST API resource. Essentially you have to ensure your integration returns proper CORS headers. For a NodeJS AWS Lambda function that could look like:
exports.handler = async (event) => {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "https://www.example.com",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
For CORS pre-flight requests to work you'd also have to ensure that OPTIONS-requests also return the correct headers. The easiest way to achieve that is to add a mock-integration for the OPTIONS-request to your OpenAPI specification. That would look like:
options:
responses:
'200':
description: Default response
headers:
Access-Control-Allow-Headers:
schema:
type: string
Access-Control-Allow-Methods:
schema:
type: string
Access-Control-Allow-Origin:
schema:
type: string
x-amazon-apigateway-integration:
type: mock
requestTemplates:
application/json: |
{"statusCode" : 200}
responses:
default:
statusCode: 200
responseParameters:
method.response.header.Access-Control-Allow-Headers: "'*'"
method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
method.response.header.Access-Control-Allow-Origin: "'https://example.com/'"
Please also mind that modern browsers don't support localhost as origin for CORS, so you might need to work around that as well.

Request validation using serverless framework

I am using serverless framework for the backend. How can I implement request validation? (do not want to write validation inside lambda functions).
To implement request validation using serverless you need to do a couple of things:
Include your model/header definitions in your stack, and then tell API gateway to use them for request validation.
You'll need to install the following packages:
serverless-aws-documentation
serverless-reqvalidator-plugin
And then you'll need to include them in your serverless.yml:
plugins:
- serverless-reqvalidator-plugin
- serverless-aws-documentation
Note: below is only a quick run-down of how to incorporate the packages. Visit the packages' documentation pages for more comprehensive examples...
Provide API gateway with a description of your models / headers.
You can import json schemas for your models, and declare http headers using the serverless-aws-documentation plugin.
Here's how you'd add a model to your serverless.yml:
custom:
documentation:
api:
info:
version: v0.0.0
title: Some API title
description: Some API description
models:
- name: SomeLambdaRequest
contentType: application/json
schema: ${file(models/SomeLambdaRequest.json)} # reference to your model's json schema file. You can also declare the model inline.
And here's how you'd reference the model in your lambda definition:
functions:
someLambda:
handler: src/someLambda.handler
events:
- http:
# ... snip ...
documentation:
summary: some summary
description: some description
requestBody:
description: some description
requestModels:
application/json: SomeLambdaRequest
You can also declare request headers against your lambda definition like so:
functions:
someLambda:
handler: src/someLambda.handler
events:
- http:
# ... snip ...
documentation:
summary: some summary
description: some description
requestHeaders:
- name: x-some-header
description: some header value
required: true # true or false
- name: x-another-header
description: some header value
required: false # true or false
Tell API gateway to actually use the models for validation
This part makes use of the serverless-reqvalidator-plugin package, and you need to add AWS::ApiGateway::RequestValidator resources to your serverless.yml file.
You can specify whether you want to validate request body, request headers, or both.
resources:
Resources:
onlyBody:
Type: AWS::ApiGateway::RequestValidator
Properties:
Name: 'only-body'
RestApiId:
Ref: ApiGatewayRestApi
ValidateRequestBody: true # true or false
ValidateRequestParameters: false # true or false
And then on individual functions you can make use of the validator like so:
functions:
someLambda:
handler: src/someLambda.handler
events:
- http:
# ... snip ...
reqValidatorName: onlyBody # reference and use the 'only-body' request validator
Put all together your lambda definition would end up looking a little like this:
functions:
someLambda:
handler: src/someLambda.handler
events:
- http:
# ... snip ...
reqValidatorName: onlyBody # reference and use the 'only-body' request validator
documentation:
summary: some summary
description: some description
requestBody:
description: some description
requestModels:
application/json: SomeLambdaRequest
requestHeaders:
- name: x-some-header
description: some header value
required: true # true or false
- name: x-another-header
description: some header value
required: false # true or false
This is now supported by Serverless framework, so there is no need to use external plugins.
To enable requests validation one need is to add the following to the serverless.yml:
HttpHandler:
handler: src/lambda/http/create.handler
events:
- http:
method: post
path: items
request:
schemas:
application/json: ${file(models/create-todo-model.json)}
Instead of keeping the file location directly under application/json you can also keep the name of the model after defining it under serverless.yml file's apiGateway section. link to documentation
Kindly note that, as of Feb-2022, serverless-offline plugin is not validating the http.request.schemas in your local. Though they are supporting deprecated version http.request.schema.
As Ivan indicated, there is no need for external plugins as this is supported by the Serverless framework. However, I think the way to configure this has changed.
functions:
create:
handler: posts.create
events:
- http:
path: posts/create
method: post
request:
schema:
application/json: ${file(create_request.json)}
This example was taken from:
https://www.serverless.com/framework/docs/providers/aws/events/apigateway/#request-schema-validators
In case you are like me and you don't want to add plugins as suggested in "https://stackoverflow.com/questions/49133294/request-validation-using-serverless-framework".
If you set parameters as required and want to validate them, you must add a request validator to your serverless.yml
Resources:
ParameterRequestValidator:
Type: AWS::ApiGateway::RequestValidator
Properties:
Name: ParameterRequestValidator
RestApiId:
Ref: ApiGatewayRestApi
ValidateRequestBody: false
ValidateRequestParameters: true
ApiGatewayMethodNameOfYourApiLookItUpInYourTemplate:
Properties:
RequestValidatorId:
Ref: ParameterRequestValidator
The method you want to validate will be named something like ApiGateway<Method><Get | Post | Patch | Put | Delete >:. You can look the name up when you package your serverless functions in the created template files.
Courtesy for this solutions goes to https://github.com/serverless/serverless/issues/5034#issuecomment-581832806
Request validation using serverless
plugins:
- serverless-python-requirements
- serverless-wsgi
- serverless-reqvalidator-plugin
- serverless-aws-documentation
provider:
name: aws
runtime: python3.8
region: us-east-1
functions:
hello:
handler: handler.hello
events:
- http:
path: /
method: get
likes:
handler: handler.likes
events:
- http:
path: /likes
method: get
integration: lambda
reqValidatorName: xMyRequestValidator
request:
passThrough: NEVER
parameters:
querystrings:
userid: true
activityid: true
template:
application/json: '{ "userid":"$input.params(''userid'')","activityid":"$input.params(''activityid'')"}'
response:
headers:
Content-Type: "'application/json'"
custom:
wsgi:
app: handler.app
pythonBin: python # Some systems with Python3 may require this
packRequirements: false
pythonRequirements:
dockerizePip: non-linux
resources:
Resources:
xMyRequestValidator:
Type: "AWS::ApiGateway::RequestValidator"
Properties:
Name: 'my-req-validator'
RestApiId:
Ref: ApiGatewayRestApi
ValidateRequestBody: true
ValidateRequestParameters: true

Serverless AWS Lambda CORS Error

I am trying to do an http request from an angularjs app to a lambda function that I had setup using serverless.
Here is my serverless.yaml function
functions:
createcustomer:
handler: handler.createcustomer
events:
- http: post /createcustomer
cors: true
Create Customer Function
module.exports.createcustomer = (event, context, callback) => {
let customer = JSON.parse(event.body).customer;
service.create(customer, function(result) {
let response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
},
body: JSON.stringify({
result: 'Created Customer Successfully',
message: 'The account has been created!',
type: 'success',
customer: result
})
};
callback(null, response);
});
};
From my AngularJS app I call it like this
app.factory('MyFactory', ['$http', function($http) {
return {
CreateCustomer: function(customer) {$http.post('<apipath>/createcustomer', {customer:customer})}
}
}]);
However, I keep getting this error:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:5000' is therefore not allowed access. The response had HTTP status code 403.
I have tried to enable CORS in the API Gateway on the POST method, but that did not change the outcome.
I've also tried setting CORS in the yaml file explicitly
functions:
createcustomer:
handler: handler.createcustomer
events:
- http: post /createcustomer
cors:
origin: '*'
Still no luck.
Does anyone know what I'm doing wrong here?
One weird thing is that I could get the post to work just fine through PostMan, but if I try it through my app it breaks.
Thanks
UPDATE
When I do serverless deploy it shows up in AWS like the picture above and the method looks like this
As I said before, I tried to enable CORS directly from the API Gateway console but there was no difference when I tried to call the method.
Your update with screenshots shows that the OPTIONS method is not set up for any of these resources. When you enable CORS for an API Gateway resource in the console, AWS sets this up automatically.
You can see this happen in the AWS console when you enable CORS for a resource, but, of course, your serverless deploy is overwriting this configuration.
To have the /createcustomer resource properly configured in AWS by the serverless deploy, you can rewrite this part of your serverless.yaml:
events:
- http: post /createcustomer
cors: true
To look like this:
events:
- http:
path: /createcustomer
method: post
cors: true
I'm not an expert in the framework's .yml syntax, so I can't explain exactly why this is.
Nonetheless, I've confirmed that a file like this:
functions:
deletecustomer:
handler: handler.deletecustomer
events:
- http:
path: /deletecustomer
method: post
cors: true
createcustomer:
handler: handler.createcustomer
events:
- http: post /createcustomer
cors: true
will create two resources in AWS API Gateway, one correctly configured for CORS, and one missing the OPTIONS method:
Here is the configuration that could help. Please note that it's always safe to be specific in allowing the CORS origins. So better to lock the origin down to localhost:{port-number}. In addition, you can also enable credentials on CORS settings. Please see the following serverless config as an example:
cors:
origin: 'http://localhost:4200'
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
allowCredentials: true
The cors configuration got moved up into the provider config:
provider:
httpApi:
cors: true

401 return from an API Gateway Custom Authorizer is missing 'Access-Control-Allow-Origin' header

In order to prevent users who have not logged in to call my lambda function through the AWS API Gateway, I'm using the Custom Authorizer lambda solution.
If the request is authorized (200) and I get a response from the called lambda everything works fine and I get the Access-Control-Allow-Origin header.
But if the request is not authorized, I get a 401 that has no Access-Control-Allow-Origin header, therefore preventing me from reading the 401 status of the response and redirecting the user to the log-in page.
I believe this is because the Custom Autorization mechanism is unaware that the request needs to use CORS. Does anyone know that this is actually the issue? Are you aware of any possible solution?
I'm happy to announce the new Gateway Responses feature which allows you to customize the error responses for requests that don't call your integration. This allows you to ensure that CORS headers are included, even on failed auth requests.
Read more in our documentation, which includes a CORS example.
Yes, this is a known bug with API Gateway custom authorizers. Thanks for bringing this to our attention. The team will update this post when we've deployed a fix. Apologies for the inconvenience.
The easiest way to resolve this for all 4XX error (including 401 errors) is to go to "Gateway Responses" and then select "Default 4XX" and then add the the header "Access-Control-Allow-Origin" with the value '*'.
See screenshot:
This works for me (inline in AWS::APIGateway: definition)
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Dev
GatewayResponses:
UNAUTHORIZED:
StatusCode: 401
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
ACCESS_DENIED:
StatusCode: 403
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
DEFAULT_5XX:
StatusCode: 500
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
RESOURCE_NOT_FOUND:
StatusCode: 404
ResponseParameters:
Headers:
Access-Control-Allow-Origin: "'*'"
The available GatewayResponses Naming are:
DEFAULT_INTERNAL
DEFAULT_4XX
DEFAULT_5XX
RESOURCE_NOT_FOUND
UNAUTHORIZED
ACCESS_DENIED
AUTHORIZER_FAILURE
AUTHORIZER_CONFIGURATION_ERROR
MISSING_AUTHENTICATION_TOKEN
INVALID_SIGNATURE
EXPIRED_TOKEN
INTEGRATION_FAILURE
INTEGRATION_TIMEOUT
API_CONFIGURATION_ERROR
UNSUPPORTED_MEDIA_TYPE
REQUEST_TOO_LARGE
BAD_REQUEST_PARAMETERS
BAD_REQUEST_BODY
THROTTLED
QUOTA_EXCEEDED
INVALID_API_KEY
WAF_FILTERED
So you could specify the Response customization for these Controlled AWS responses.
Because it took me a while to figure out how to put it all together in Cloud Formation, here is a snippet showing how to set it up.
...
MyApi:
Type: "AWS::ApiGateway::MyApi"
Properties:
Description: My API
Name: "my-api"
MyApiAuthorizer:
Type: "AWS::ApiGateway::Authorizer"
Properties:
Name: "my-api-authorizer"
IdentitySource: "method.request.header.Authorization"
ProviderARNs:
- !GetAtt MyUserPool.Arn
RestApiId: !Ref MyAApi
Type: COGNITO_USER_POOLS
MyApiGatewayResponse:
Type: "AWS::ApiGateway::GatewayResponse"
Properties:
ResponseParameters:
"gatewayresponse.header.Access-Control-Allow-Origin": "'*'"
"gatewayresponse.header.Access-Control-Allow-Headers": "'*'"
ResponseType: UNAUTHORIZED
RestApiId: !Ref MyApi
StatusCode: "401"
If, like me, you are running into problems with API Gateway V2, specifically with an HTTP API - the ANY method doesn't seem to work with the plug and play CORS offering. I had to individually create a route for each method (annoying because they all call the same lambda function, but oh well).
Adding to the answers above, if you're not using Cloudformation/SAM template, you can save yourself some manual steps using this python script:
import boto3
import sys
if len(sys.argv) != 3:
print("usage: python script.py <API_ID> <STAGE>")
exit()
client = boto3.client('apigateway')
response = client.put_gateway_response(
restApiId=sys.argv[1],
responseType='UNAUTHORIZED',
statusCode='401',
responseParameters={
"gatewayresponse.header.Access-Control-Allow-Origin": "'*'",
"gatewayresponse.header.Access-Control-Allow-Headers": "'*'"
}
)
response = client.create_deployment(
restApiId=sys.argv[1],
stageName=sys.argv[2])
To fix this for our SPA (we are using AWS Cognito authorizer) we added the next Response Headers in DEFAULT 4xxx and DEFAULT 5xxx Gateway responses:
Access-Control-Allow-Origin '{url_of_your_front-end}'
Access-Control-Allow-Headers '{url_of_your_front-end}'
we set {url_of_your_front-end} instead of '*', because the browser didn't like it :D
as an additional we set Access-Control-Allow-Credentials 'true' header to make a browser happy.
Picture with all headers
Adding the response header "Access-Control-Allow-Origin" for the Unauthorized response should fix this issue
Select the Api
Select the Gateway Responses
Select Unauthorized
Edit the response header and add Access-Control-Allow-Origin with value "*"