AWS API Gateway: form-data support - amazon-web-services

Is it possible to send request with: Content-Type: multipart/form-data to API Gateway?
In my case, I try to send form-data like below via Postman:
user[email]:extest829#ex.com
user[password]:password
user[password_confirmation]:password
user[username]:testUser
But It seems that API Gateway loses the content.
Everything works fine when I send it as: application/x-www-form-urlencoded or application/json.

Using mulipart/form-data is not fully supported by AWS API Gateway, especially when we try to send file via mulipart/form-data.
To send image along with other data from form, probably the best solution would be send it as JSON, where image is encoded with base64.
For example, when someone want to send:
Username (string)
Avatar (image)
Avatar should be encoded with base64. It gives an image as a text, like:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyA...
The content of POST message would be:
{
"user_account": {
"avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyA...",
"username": "my name"
}
}
API Gateway
In API Gateway, under your API, open Models and create new model, for example UserAccount:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "UserAccountUpdate",
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"avatar": { "type": "string" },
"username": { "type": "string" }
}
}
}
}
In Method Request -> HTTP Request Headers set: Content-Type.
In Method Request -> Request Body set: application/json and as model use created UserAccount model.
In Integration Request -> Content Handling set as: Passthrough.
In Integration Request -> Body Mapping Templates choose: When no template matches the reuqest Content-Type header. (You can also use two other options here and set additional mapping templates).
Backend
Backend receives an image as a text encoded with base64. So probably before it uses futher, it needs to decode it from text to image.
Encoding/decoding images with base64 is quite popular, so you should find some appropriate tools / libs in your frameworks.

When you are sending request with Content-Type: multipart/form-data to API Gateway, you need to pass through your original Content-Type header to your integration endpoint.
aws apigateway update-integration \
--rest-api-id a1b2c3d4e5 \
--resource-id a1b2c3 \
--http-method POST \
--patch-operations op='replace',path='/requestParameters/integration.request.header.Content-Type',value='method.request.header.Content-Type'

Code example for managing binary data in AWS Gateway and Proxy+ Lambda. Or to upload and retrieve files from servers using AWS Gateway and Proxy Lambda+
Click here

Related

APIGateway does not perform request validation when called using POSTMan

Just learning my way through AWS - I have an APIGateway REST API setup with Lambda proxy integration. The API has a model defined, and request validation setup on the body using this model.
Say the model is
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"propertyA": {
"type": "string"
},
"propertyB": {
"type": "string"
},
"propertyC": {
"type": "string"
},
"propertyD": {
"type": "string"
}
},
"required": ["propertyA", "propertyB", "propertyC", "propertyD"]
}
Now, if I test the API via APIGateway console, and purposely give an invalid input (omitting a required property propertyD):
{
"propertyA": "valueA",
"propertyB": "valueB",
"propertyC": "valueC"
}
the request fails with the error(400): Sun Jul 11 13:07:07 UTC 2021 : Request body does not match model schema for content type application/json: [object has missing required properties (["propertyD"])]
But when I invoke the same API(and stage) with the same invalid input from Postman, the validation seems to be not happening, and request is proxied to Lambda, which even returns a 200 OK as long as I comment out the parts of code that depend on propertyD.
What's the difference here? Should I be passing in any request header from the client side? I couldn't find anything from the AWS documentations
Answering my question-
Issue was with the headers used in the request - Postman defaulted the JSON as a Content-Type of text/plain, I had to switch to JSON using the dropdown in Body tab to make PostMan set the Content-Type to application/json
Following this post seems to have fixed the problem: https://itnext.io/how-to-validate-http-requests-before-they-reach-lambda-2fff68bfe93b, although it doesn't explain how
Apparently the magic lies with the config adding Content-Type Header under HTTP Request Headers section, even though the header is set correctly as application/json in PostMan.

Executing AWS Cloud Watch APIs through HTTP requests

I am currently trying to execute the AWS cloud watch APIs and get the logs from the cloud watch environment. I have successfully implemented this by using the SDK provided by AWS. Also in the documentations they mentioned that we can also use HTTP requests to get the logs from cloud watch. In the following documentation they have mentioned the params that we have to pass from the request.
https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html.
The problem is there are also some common params to be sent from the request. I need to clarify the way that we want to send them. As query params, path params or request headers?
The request parameters for the Cloud Watch Rest API should be sent in the JSON format inside the {}, similar to the way in the POST example given. Of these only logGroupName is required, while the other paramaters mentioned are optional:
**
endTime
filterPattern
interleaved
limit
logGroupName
logStreamNamePrefix
logStreamNames
nextToken
startTime
**
In the context of the entire HTTP request:
POST / HTTP/1.1
Host: logs.<region>.<domain>
X-Amz-Date: <DATE>
Authorization: AWS4-HMAC-SHA256 Credential=<Credential>, SignedHeaders=content-type;date;host;user-agent;x-amz-date;x-amz-target;x-amzn-requestid, Signature=<Signature>
User-Agent: <UserAgentString>
Accept: application/json
Content-Type: application/x-amz-json-1.1
Content-Length: <PayloadSizeBytes>
Connection: Keep-Alive
X-Amz-Target: Logs_20140328.FilterLogEvents
{
"endTime": number,
"filterPattern": "mystring",
"interleaved": boolean,
"limit": number,
"logGroupName": "string",
"logStreamNamePrefix": "string",
"logStreamNames": [ "string" ],
"nextToken": "string",
"startTime": number
}
The Common Parameters are sent as HTTP params as seen in the example above. They are needed here for signing your requests to AWS with the proper authentication.
(Authentication occurs automatically in the background when using the CLI)
This is an official walkthrough of how to construct a canonical,signed HTTP request for AWS APIs
For example:
Action=ListUsers&
Version=2010-05-08&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIDEXAMPLE%2F20150830%2Fus-east-1%2Fiam%2Faws4_request&
X-Amz-Date=20150830T123600Z&
X-Amz-SignedHeaders=content-type%3Bhost%3Bx-amz-date

Difference in request body in aws api gateway test and curl

I'm trying to add a POST HTTP method to my AWS API Gateway. I'm using SAM framework with Python.
I find that there is a difference in the "body" of the response when it is generated from my desktop (curl or postman) and the AWS API Gateway 'TEST'
Right now, the "POST" command only prints the 'event' object received by the lambda_handler. (I'm using an object to store the event as you can see below)
def add(self):
response = {
"statusCode": 200,
"body": json.dumps(self._event)
}
return response
When I'm using the 'TEST' option of the API Gateway console, with the input:
{"username":"xyz","password":"xyz"}
I receive the following output:
{
"body": "{\"username\":\"xyz\",\"password\":\"xyz\"}",
<the rest of the response>
}
However, when I'm sending the curl (or postman) request:
curl --header "Content-Type: application/json" --request POST --data '{"username":"xyz","password":"xyz"}' <aws api gateway link>
I get the following response:
{
"body": "eyJ1c2VybmFtZSI6Inh5eiIsInBhc3N3b3JkIjoieHl6In0="
<the rest of the response>
}
Why do you think there is a difference between the two tests?
Curl and Postman seem to be automatically Base64 encoding your Authentication credentials.
The responses are the same. The latter response is a Base64-encoded token of the first response.

How to use Custom Authorizer on Api Gateway?

Custom Authorization
Lambda Execution Role : Full Access Api Gateway and Lambda
Token Source: method.request.header.Authorization
Token Validation: blank
Add this custom authorization to api method request . Authorizers test is succes but ı request to api on Postman then 401.
{
"message": "Unauthorized"
}
Custom Authorizer Lambda
console.log('Loading function');
exports.handler = function(event, context, callback) {
console.log("event:",JSON.stringify(event)); console.log("event:",JSON.stringify(context));
console.log('Client token: ' + event.authorizationToken);
console.log('Method ARN: ' + event.methodArn);
callback(null, {
"principalId": "18",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1459758003000",
"Effect": "Allow",
"Action": [
"execute-api:Invoke"
],
"Resource": [
"arn:aws:execute-api:*"
]
}
]
}
});
}
Postman Code:
curl --request GET \
--url {url} \
--header 'authorization: Test Token' \
--header 'cache-control: no-cache' \
--header 'content-type: application/json' \
--header 'postman-token: c9110a92-414e-e1aa-61fb-194758dace86'
Solution
Token Source: Authorization
API Gateway has changed, as of today Jan 18. Following needs to be know.
If the Method's Integration Request is not lambda proxied. While creating custom authorier (in Authorizer tab) , you should enter Token Source as "Authorization" and not "method.request.header.Authorization". Also , in the Method Request tab of the method ( eg GET) , you should set the HTTP Header Mapping , for 'Authorization'.
If the Method's Integration Request is proxied , no mapping is required. All the request body+header+parameter+AWSextraStuff is avaliable in the event[] object of lambda. Hence no mapping is required.
Few more pitfall be careful.
- Use standard string like 'Authorization' ( which is a standard) , is you use different string , change every where.
- The authorization token when passed to lambda, for no proxied integration request, as event['authorizationToken']and not event['Authorization']
- If you get error like Lambda Malform... , it is because you are using Lambda Proxy and it requires response in specific format, your not sending data in tha format.
- If your using Postman , switch to 'raw' against 'pretty' mode.
To call an API with the custom TOKEN authorizer
Open Postman, choose the GET method and paste the API's Invoke URL into the adjacent URL field.
Add the custom authorization token header and set the value to allow. Choose Send.
Worth read - http://docs.aws.amazon.com/apigateway/latest/developerguide/use-custom-authorizer.html#call-api-with-api-gateway-custom-authorization
You don't call the Custom Authorizer through Postman, that's API Gateways' job.
Every time that you call an endpoint protected by your custom authorizer the API Gateway will check if the value of the given Authorization Header exists in its Policies Cache. If the value does not exist, your Custom Authorizer will be called to authenticate the request.
A simple representation of the flow:
Lambda Handler (handles a protected endpoint GET /users/{id})
|
| ------------
| |
| Custom Authorizer
| /
| / (if the request is not authorized yet)
| /
Api Gateway
|
|
|
Request (with Authorization Header)
You just have to set the Authorization of your resource method to your custom authorizer and let the API Gateway do all the work.

Add JSON object in Body of API Gateway to Kinesis proxy?

I have configured API gateway as a Kinesis proxy as described in Amazon’s tutorial for putting record into Kinesis stream.
The HTTP headers for integration request are:
stream-name is mapped to method.request.header.stream-name
partition-key is mapped to method.request.header.partition-key
Content-Type is mapped to application/x-amz-json-1.1
The body mapping template for content-type application/json looks like:
{
"StreamName": "$input.params('stream-name')",
"PartitionKey": "$input.params('partition-key')",
"Data": "$util.base64Encode({"rows": "$input.json('$')", "uuid": "$input.params('uuid')"})"
}
The data comes in as a JSON-request payload. We should add a uuid parameter as a JSON-object key into the data-payload for Kinesis, but the encoded data being sent to Kinesis isn’t a JSON object.
It is required to add a parameter uuid as a json-object key into the data-payload for kinesis. The problem I am facing is, that encoded data sent to kinesis is not a JSON object.
The data I expect to be sent to Kinesis should be a JSON object:
{
"rows": [{"id": 1, "name": "a"}, {"id": 2, "name": "b"}],
"uuid": "0001"
}
But the actual data sent to Kinesis looks like:
{
rows=[{"id": 1, "name": "a"}, {"id": 2, "name": "b"}],
uuid=0001
}
How can I send a JSON object from API gateway to Kinesis? I have been experimenting with $util.parseJson but couldn't find a solution for this scenario.
You need to escape double quotes in velocity templates
#set($event = "{
""rows"": ""$input.json('$')"",
""uuid"": ""$input.params('uuid')"",
}")
{
"StreamName": "$input.params('stream-name')",
"Data": "$util.base64Encode($event)",
"PartitionKey": "$input.params('partition-key')"
}
Here is a similar question on the aws forums for sending data to kinesis through api gateway https://forums.aws.amazon.com/thread.jspa?threadID=233060