Search code examples
aws-lambdaamazon-cloudfront

CloudFront Lambda@Edge HTTPS redirect


I have a CloudFront distribution with a Lambda function attached to the viewer request hook. I'm using this to redirect to the canonical domain (eg. www.foo.tld -> foo.tld). I also have the distribution itself set up to redirect HTTP -> HTTPS.

The problem is that this requires clients to potentially have to do 2 requests to get to the correct URL. For example:

http://www.foo.tld/ -> https://www.foo.tld/ (performed by CloudFront)
https://www.foo.tld/ -> https://foo.tld/ (performed by Lambda function attached to viewer request hook)

I would like to have this done in 1 request:

http://www.foo.tld/ -> https://foo.tld/

It looks like I need to add this functionality to the Request Event, but the documentation seems to indicate the protocol is not exposed to the Lambda function in the request event.

My question is:

  • How do I expose the protocol to the Lambda function attached to the Viewer Request hook?
  • Alternately, is there a better way to do this?

Solution

  • Side note: redirects that change both the hostname and the scheme may be problematic, more in the future than now, as browsers become less accepting of HTTP behavior without TLS. I am at a loss, at the moment, to cite a source to back this up, but am under the impression that redirecting directly from http://www.example.com to https://example.com should be avoided. Still, if that's what you want...


    CloudFront and Lambda@Edge support this, but only in an Origin Request trigger.

    If you whitelist the CloudFront-Forwarded-Proto header in the Cache Behavior settings, you can then access that value like this:

    const request = event.Records[0].cf.request; // you may already have this
    const scheme = request.headers['cloudfront-forwarded-proto'][0].value;
    

    The value of scheme will either be http or https.

    I'm a little bit pedantic, so I like a failsafe. This alternative version will always set scheme to https and avoid the exception that will be thrown if for whatever reason the header is not there. This may or may not suite your taste:

    const request = event.Records[0].cf.request; // you may already have this
    const scheme = (request.headers['cloudfront-forwarded-proto'] || [{ value: 'https' }])[0].value;
    

    The reason this can only be done in an Origin Request trigger is that CloudFront doesn't actually add this header internally until after the Viewer Request trigger has already fired, if there is one.

    But note also that you almost certainly want to do this in an Origin Request trigger -- because responses from these triggers can be cached... which should mean faster responses and lowered costs. Whitelisting the header also adds it to the cache key, meaning that CloudFront will automatically cache separate HTTP and HTTPS responses for any given page, and only replay them for identical requests.

    See also https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-cloudfront-star-headers