Last week we ran into a problem where a new lambda didn't get invoked by the API Gateway even though the Authorizer had returned a seemingly correct response. It turned out that this exception: "Execution failed due to configuration error: Invalid JSON in response: Can not deserialize instance of java.lang.String out of START_ARRAY token"
was thrown when the API Gateway did the integration request to the lambda.
The overview of the solution looks like this:
The tricky thing with this specific problem is that you will not see any errors unless you know where to look. So let's break down the overview above into steps and look at where the exception happens:
- Client does request and sends in a JWT in the Authorization header
- API Gateway detects that the requested API has an Authorizer and does not have any cached authorizer-response for this JWT.
- API Gateway calls the Authorizer with the JWT in the Authorizer header.
- The Authorizer verifies the JWT and respons with a JSON object containing 3 properties principalId, policyDocument and context.
- The API Gateway tries to do a Lambda proxy integration request
- Lambda calls Dynamodb and returns response
So the problem we faced was that we created a malformed context property in step 4, by setting some of the properties of the context object to other value types than the approved string, number and bool. But there's no validation when we return the response from the Authorizer. Instead the problem happens in step 5 when API Gateway does the integration request to the proxy lambda with a malformed context object. The first symptom is simply that the request doesn't reach the lambda. So what we needed to do to find the problem was to look at the logs of the API Gateway itself. These logs can be a bit hard to find unless you know where to look. Firstly if it isn't already activated, you need to activate logging in API Gateway by doing this:
- Go to AWS API Gateway
- Click on the API you want to activate logs for
- Click on Stages
- Click on the stage you want to activate logs for
- Click on the Logs/Tracing tab
- Check Enable Cloud Watch logs
- Keep Log level: Error (to only get the faulty logs)
- Check Log full requests/responses data
Now after trying the request again, we can go to CloudWatch logs. The log group will start with API-Gateway-Execution-Logs_{the-id-of-you-api}. To find the id of your API go to API Gateway. The id will be visible at the top. When we found the log group we searched for "error" and could then find the exception row: "Cannot deserialize" exception. Just before that we can see the Authorizer response:
To fix this problem we created a generic stringify-complex-properties method in our Authorizer that looks like this:
/**
* Will go through and stringify any properties that are of type 'object'.
* @param object
* @returns mutated object
*/
const stringifyComplexProperties = (object: any) => {
for (let p of Object.keys(object)) {
let prop = object[p];
if (typeof prop === 'object') {
object[p] = JSON.stringify(prop);
}
}
return object;
};
Adding a call to the stringifyComplexProperties
method fixed the problem and the API Gateway could now successfully do the integration request towards the lambda:
return {
principalId,
context: stringifyComplexProperties(contextObject),
policyDocument: {
Version: '2012-10-17',
Statement: statements
}
};
AWS has some good documentation regarding the Lambda Authorizer that also states clearly what is OK to set in the context property. But hopefully this post can help anyone else that fails to read the docs 😉:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
Top comments (2)
U saved me few hrs Man . Thank U
You save my day. Big thanks