loading...

Basic Auth on Lambda + Api Gateway + Cloudfront: solving the 401 Unauthorized error.

dvddpl profile image Davide de Paolis ・4 min read

Recently we needed to restrict access /add some basic level of security to an API we are providing to another department. The choice went for Basic Auth ( I know it's kinda old and well.. basic, but the endpoint was supposed to be for internal use anyway )
shrugs

In order to add this basic authorization in our Lambda handler, we implemented a Middy middleware.
This middleware is in charge of retrieving the authorized users from SSM ParameterStore and then find a match with the token received in the Authorization Header ( by using
basic-auth module )

Unit tests on the middleware as well on the handler were working.
Authorization header was properly parsed and the user was granted access or denied if not in the list ( or if no Auth token was passed)

Once deployed though, nothing was working.

Access Denied

While Testing with Postman the first thing we realized was that Authorization header ( all headers actually) were Capitalized, while the source code of basic-auth was checking for headers.authorization

suspicious

Headers are case INsensitive.

According to the docs HTTP Headers are case insensitive (see also this discussion on stackoverflow) but honestly, this statement does not make much sense to me.

They might be case insensitive, but when you debug the object in your code, they are either capitalized or lowercase, therefore accessing the property as lowercase or capitalized makes indeed a difference.
It might be APIGateway or Lambda runtime or Node itself, I dunno, but since what we got as Lambda Event was headers.Authorization basic-auth could not find anything under headers.authorization.

I quickly run a test on Gateway API console to check if there was some conversion there:

Execution log for request 299538fb-5d1f-407d-8efb-aadf77e27ae6
Thu Aug 06 10:32:33 UTC 2020 : HTTP Method: GET, Resource Path: /MY_ENDPOINT
Thu Aug 06 10:32:33 UTC 2020 : Method request query string: {foo=1, bar=2}
Thu Aug 06 10:32:33 UTC 2020 : Method request headers: {authorization=*************************bzTjmQ=}
Thu Aug 06 10:32:33 UTC 2020 : Endpoint request body after transformations: {"resource":"/MY_ENDPOINT","path":"/MY_ENDPOINT","httpMethod":"GET","headers":{"authorization":" Basic *************************bzTjmQ="},"queryStringParameters":{"foo":"1","bar":"2"} [TRUNCATED]
Thu Aug 06 10:32:34 UTC 2020 : Endpoint response body before transformations: {"statusCode":401,"body":"Access denied"}

No, no conversion there.

By running a Sample Gateway APIProxy within the Lambda console though, it is clear that all headers are supposed to be capitalized

"headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, sdch",
    "Accept-Language": "en-US,en;q=0.8",
    "Cache-Control": "max-age=0",
    "CloudFront-Forwarded-Proto": "https",
    "CloudFront-Is-Desktop-Viewer": "true"
}

It is therefore very likely that the event header passed to the handler has everything capitalized. Whatever, it is just a matter of reading the right property from basic-auth. Luckily they provide an additional method to parse the authorization from any path/object: quickly switch from auth to parse and deploy!

But... The APIGateway URL endpoint was working fine, still when invoking the API through Cloudfront the Access Denied error was still occurring.

sad

After some investigation we found out that for GET requests Cloudfront removes the Authorization header field before forwarding the request to the origin. (see docs)[https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html#request-custom-headers-behavior]

GET and HEAD requests – CloudFront removes the Authorization header field before forwarding the request to your origin.
OPTIONS requests – CloudFront removes the Authorization header field before forwarding the request to your origin if you configure CloudFront to cache responses to OPTIONS requests.
DELETE, PATCH, POST, and PUT requests – CloudFront does not remove the header field before forwarding the request to your origin.

How to change this Behaviour?

Simply whitelist the Authorization header!

From UI Console go to Cache Behaviour Setting and Edit
select Whitelist under Cache Based on Selected Request and then add Authorization under Whitelist Headers

Whitelist Headers on Cloudfront

If, like us, you are deploying with AWS CDK specify which headers must be forwarded by the caching behavior:

{
 behaviors:[
            {
              allowedMethods: CloudFrontAllowedMethods.ALL,
              cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD,
              defaultTtl: Duration.days(1),
              pathPattern: "/MY-ENDPOINT",
              forwardedValues: {
                queryString: true,
                queryStringCacheKeys: ["foo","bar"],
                headers: ["Authorization", "authorization"]
              }
       }
   ]
}

Redeploy, wait a bit for CloudFront to invalidate the distribution and propagate the changes and
request authorization is validated and properly cached for the following request!

happy dance
Hope it helps

Posted on by:

dvddpl profile

Davide de Paolis

@dvddpl

Sport addicted, productivity obsessed, avid learner, travel enthusiast, expat, 2 kids. πŸ‚βœˆπŸšžπŸŒπŸ“·πŸ–₯πŸ€˜πŸ‘¨β€πŸ‘©β€πŸ‘¦β€πŸ‘¦πŸš€ (Opinions are my own)

Discussion

pic
Editor guide