Basic Auth on Lambda + Api Gateway + Cloudfront: solving the 401 Unauthorized error.
Tags: serverless, aws, authorization, javascript
Published: 2020-08-07 09:43:52

---
title: Basic Auth on Lambda + Api Gateway + Cloudfront: solving the 401 Unauthorized error.
published: true
description: struggling with authorizaion header and cloudfront? here's the solution
tags: #serverless, #aws, #authorization, #javascript
//cover_image: https://direct_url_to_image.jpg
---

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](https://media.giphy.com/media/SiEvTIb0AyO8BjlI3I/giphy.gif)

In order to add this basic authorization in our Lambda handler, we implemented a [Middy middleware](https://github.com/middyjs/middy#writing-a-middleware).
This middleware is in charge of retrieving the authorized users from [SSM ParameterStore](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) and then find a match with the token received in the **Authorization Header** ( by using 
[basic-auth module](https://www.npmjs.com/package/basic-auth) )

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](https://dev-to-uploads.s3.amazonaws.com/i/qy09sbti0womr76wwotg.png)

While Testing with [Postman](https://www.postman.com/) 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](https://media.giphy.com/media/p26ylSePJM12g/giphy.gif)

### Headers are case INsensitive.  
According to the [docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) HTTP Headers are case insensitive (see also [this discussion on stackoverflow](https://stackoverflow.com/questions/5258977/are-http-headers-case-sensitive#:~:text=HTTP%20header%20names%20are%20case%2Dinsensitive%2C%20according%20to%20RFC%202616%3A&text=4.2%3A-,Each%20header%20field%20consists%20of%20a%20name%20followed%20by%20a,Field%20names%20are%20case%2Dinsensitive)) 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](https://media.giphy.com/media/Ty9Sg8oHghPWg/giphy.gif)

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](https://dev-to-uploads.s3.amazonaws.com/i/6vwfuuasc6i5xz2qr0cj.png)

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](https://media.giphy.com/media/l3q2Z6S6n38zjPswo/giphy.gif)
Hope it helps