Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

API Gateway Custom Authorizer: Control error message and code

I have a custom authorizer for my Gateway API. I've read many articles on how to customize the error message and code returned to end user when on authentication or authorization errors. This one seemed the most useful.

Problem is the API Gateway doesn't behave as documented.

My custom authorizer implementation (python):

def lambda_handler(event, context):
    raise Exception('the sky is falling!')

When I call the API using curl:

kash@Laptop$ date; curl -i -X GET -H "Authorization: Bearer 1234abcd`date +%s`" https://xxxx.execute-api.us-west-2.amazonaws.com/prod/ticket
Mon Jun  4 12:27:51 CDT 2018
HTTP/1.1 500 Internal Server Error
Date: Mon, 04 Jun 2018 17:27:53 GMT
Content-Type: application/json
Content-Length: 16
Connection: keep-alive
x-amzn-RequestId: 9cc6d7ce-681c-xxxx-8a4a-23a7616ba4a5
x-amzn-ErrorType: AuthorizerConfigurationException
x-amz-apigw-id: xxxx=

{"message":null}
kash@Laptop$ 

How do I make it return HTTP 4xx with {"message": "the sky is falling!"} ?


For debugging: I went to Gateway Responses under my API and updated the "Body Mapping Templates" for "Authorizer Configuration Error (500)" from this:

{"message":$context.error.messageString}

to this:

{
      "errorMessage":"$errorMessage",
      "messageString":"$messageString",

      "context.errorMessage":"$context.errorMessage",
      "context.messageString":"$context.messageString",

      "context.error.errorMessage":"$context.error.errorMessage",
      "context.error.messageString":"$context.error.messageString",

      "context.authorizer.error.errorMessage":"$context.authorizer.error.errorMessage"
      "context.authorizer.error.errorMessage":"$context.authorizer.error.errorMessage"
      "context.authorizer.errorMessage":"$context.authorizer.errorMessage"
      "context.authorizer.messageString":"$context.authorizer.messageString"

      "type": "$context.error.responseType",
      "statusCode": "'404'",
      "stage": "$context.stage",
      "resourcePath": "$context.resourcePath",
      "stageVariables.a": "$stageVariables.a",

      "context.apiId": "$context.apiId",
      "context.authorizer.claims.property": "$context.authorizer.claims.property",
      "context.authorizer.principalId": "$context.authorizer.principalId",
      "context.authorizer.property": "$context.authorizer.property",
      "context.httpMethod": "$context.httpMethod",
      "context.error.message": "$context.error.message",
      "context.error.messageString": "$context.error.messageString",
      "context.error.responseType": "$context.error.responseType",
      "context.extendedRequestId": "$context.extendedRequestId",
      "context.identity.accountId": "$context.identity.accountId",
      "context.identity.apiKey": "$context.identity.apiKey",
      "context.identity.apiKeyId": "$context.identity.apiKeyId",
      "context.identity.caller": "$context.identity.caller",
      "context.identity.cognitoAuthenticationProvider": "$context.identity.cognitoAuthenticationProvider",
      "context.identity.cognitoAuthenticationType": "$context.identity.cognitoAuthenticationType",
      "context.identity.cognitoIdentityId": "$context.identity.cognitoIdentityId",
      "context.identity.cognitoIdentityPoolId": "$context.identity.cognitoIdentityPoolId",
      "context.identity.sourceIp": "$context.identity.sourceIp",
      "context.identity.user": "$context.identity.user",
      "context.identity.userAgent": "$context.identity.userAgent",
      "context.identity.userArn": "$context.identity.userArn",
      "context.integrationLatency": "$context.integrationLatency",
      "context.path": "$context.path",
      "context.protocol": "$context.protocol",
      "context.requestId": "$context.requestId",
      "context.requestTime": "$context.requestTime",
      "context.requestTimeEpoch": "$context.requestTimeEpoch",
      "context.resourceId": "$context.resourceId",
      "context.resourcePath": "$context.resourcePath",
      "context.responseLength": "$context.responseLength",
      "context.responseLatency": "$context.responseLatency",
      "context.status": "$context.status",
      "context.stage": "$context.stage"
 }

and the response is:

 {
      "errorMessage":"",
      "messageString":"",

      "context.errorMessage":"",
      "context.messageString":"",

      "context.error.errorMessage":"",
      "context.error.messageString":"null",

      "context.authorizer.error.errorMessage":""
      "context.authorizer.error.errorMessage":""
      "context.authorizer.errorMessage":""
      "context.authorizer.messageString":""

      "type": "AUTHORIZER_CONFIGURATION_ERROR",
      "statusCode": "'404'",
      "stage": "prod",
      "resourcePath": "/ticket",
      "stageVariables.a": "",

      "context.apiId": "xxxx",
      "context.authorizer.claims.property": "",
      "context.authorizer.principalId": "",
      "context.authorizer.property": "",
      "context.httpMethod": "GET",
      "context.error.message": "",
      "context.error.messageString": "null",
      "context.error.responseType": "AUTHORIZER_CONFIGURATION_ERROR",
      "context.extendedRequestId": "xxxx=",
      "context.identity.accountId": "",
      "context.identity.apiKey": "",
      "context.identity.apiKeyId": "",
      "context.identity.caller": "",
      "context.identity.cognitoAuthenticationProvider": "",
      "context.identity.cognitoAuthenticationType": "",
      "context.identity.cognitoIdentityId": "",
      "context.identity.cognitoIdentityPoolId": "",
      "context.identity.sourceIp": "xxx.244.xxx.2",
      "context.identity.user": "",
      "context.identity.userAgent": "curl/7.47.0",
      "context.identity.userArn": "",
      "context.integrationLatency": "",
      "context.path": "/prod/ticket",
      "context.protocol": "HTTP/1.1",
      "context.requestId": "57e2462d-681c-xxxx-7dd93186dc68",
      "context.requestTime": "04/Jun/2018:17:25:57 +0000",
      "context.requestTimeEpoch": "1528133157762",
      "context.resourceId": "pz9fb8",
      "context.resourcePath": "/ticket",
      "context.responseLength": "",
      "context.responseLatency": "",
      "context.status": "",
      "context.stage": "prod"


 }

I've read:

  • Is there a way to change the http status codes returned by Amazon API Gateway?
  • How to throw custom error message from API Gateway custom authorizer
  • custom authorizers in Amazon API Gateway 500 error
  • Is it possible to customize API Gateway custom authorizer response message and status code on unauthorized?

and some more on AWS forums.

like image 771
Kashyap Avatar asked Jun 04 '18 17:06

Kashyap


1 Answers

In case it helps someone:

CA = custom authorizer

  • error code: AWS doesn't fully allow a CA implementation to dictate the error code sent back to caller.
    • If the CA returns an Auth Policy which does not have resource/method that was invoked in one of the statements with action Allow, then user gets a 403 with something like "Not authorized to access resource"
    • If the CA returns an Auth Policy which has statements with action Deny that contains resource/method that was invoked, then user gets a 403 with something like "access denied explicitly with a Deny"
    • If the Exception raised by CA has message "Unauthorized" then user gets 401 with message "Unauthorized".
    • If CA throws an exception with any other message then user gets HTTP-500 internal server error (Authorizer Configuration Error) and call is rejected/not-authorized.
  • error message: Only static control is allowed via Body Mapping Template in Gateway Responses.
    • E.g. you can update the Body Mapping Template for "Unauthorized [401]" in "Gateway Responses" to say "My service doesn't like you for some unknown reason" and then whenever CA throws "Unauthorized" exception the end user gets HTTP 401 with "My service doesn't like you for some unknown reason".
    • Similarly you can also update "Access Denied [403]" or "Authorizer Configuration Error [500]". But the message is static and can not be controlled from CA implementation.
    • It is NOT possible to have different 401 messages like:
    • 401: Unauthorized due to expired token.
    • 401: Unauthorized due to missing scope.

Other unrelated thing: Because the CA throws an exception in certain conditions to convey auth failure, from a metric point of view this increments the Lambda ErrorCount metric. So that metric isn't reliable to identify "application errors".

like image 186
Kashyap Avatar answered Oct 05 '22 00:10

Kashyap