AWS CloudFront Signed Cookie Laravel Middleware

Steve Barbera
5 min readMar 11, 2022
Cloud(Front) Cookies

Intro

When a issue comes across and it takes me 6+ hours to figure out the problem by changing values one at a time, those are the real programming challenges. They are frustrating at the time, but once they are solved it is a very rewarding experience.

I wanted to use AWS CloudFront CDN to cache images and audio files for an application I was building. The problem is if someone figured out the file name path they would be able to data mine all the files in the S3 bucket. There are two methods (that I know of) that you can use to protect your assets. The first is using Signed URLs which are prepended to the media url path, this method works well but you would need to write a hook to pre-sign every CDN asset before sending it to the page to render. The other method is a CloudFront Signed Cookies. I chose to go with this method for my project to serve the protected assets.

The major problem with trouble-shooting issues with CloudFront are the error messages are extremely vague. You can turn on CloudFront logging to a S3 bucket but the error messages are of no hope. I spent many errors changing single variables at a time to figure out the issue.

For all your sake I’m going to document the technique I used to write a Laravel middleware to create the CloudFront Signed Cookies for my app.

CloudFront Setup

For the sake of this tutorial we are going to assume you already have a S3 bucket setup with your assets. Also you will need a domain name that is hosted live on the internet.

Create a new public private keys

In your terminal create a new private key with:
openssl genrsa -out private_key.pem 2048

Next run:
openssl rsa -pubout -in private_key.pem -out public.key.pem
to extract the public key from the public_key.pem.

More info can be found here:
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html

Create a new CloudFront Distribution

Make sure you CDN is the same domain as the site you want to retrieve assets from. If your domain name is example.com your CloudFront domain should be something like assets.example.com or cdn.example.com . The reason for this is you are going to be creating cookies that work on a domain that you
own. You won’t be able to create a cookie for another site.

Create a new CloudFront distribution.
Under “Origin domain” select the S3 bucket with your assets.

Under the “Default cache behavior” panel select “YES” under “Restrict viewer access” and add the key group that you created.

In the “Settings” panel add a custom SSL certificate with your domain eg: *.example.com
Under “Alternate domain name (CNAME)” create a new item with the domain you want for your CloudFront distribution like: assets.example.com or cdn.example.com .

Create your CloudFront distribution, allow CloudFront to update the S3 bucket policy, this should take a bit about 10 minutes or so.

Add a new Route 53 Record

Under your domains hosted zone add new a record for your CloudFront distribution. Set the record name to be the same name you set your CloudFront CNAME.
Beneath the record name change the Alias toggle, you should see the CloudFront distribution in the drop down list.
If you don’t see your distribution in the drop down you can use record type “CNAME” and for the value use the CloudFront Domain name which should look like: abc14ddad499.cloudfront.net . Create the record in the Route53 hosted zone.

Update the S3 Bucket CORS Policy

Select the S3 bucket with your assets and select “Permissions” tab. Towards the bottom in the CORS Policy panel update the policy to match below. Change the example.com to your domain and your CDN domain. (Note: the CDN domains may note be needed, but this solution worked for me).

Setup Laravel

In your laravel application install the aws/aws-sdk-php
library: https://github.com/aws/aws-sdk-php

composer require aws/aws-sdk-php

Add the private_key.pem private key file you created to the app/storage directory.

In your .env file add the following:

# Found under CloudFront -> Public Keys
CLOUDFRONT_KEY_ID="Cloudfront Public Key ID"
# The domain name of your cloudfront distribution
CLOUDFRONT_RESOURCE_KEY="https://cdn.example.com/*"

Under config/services.php add the following array values:

'cloudfront' => [
'key_id' => env('CLOUDFRONT_KEY_ID'),
'resource_key' => env('CLOUDFRONT_RESOURCE_KEY'),
],

Create the CloudFront Cookie Middleware

Using artisan create a new middleware:
php artisan make:middleware CloudFrontCookie

Edit the app/Http/Middleware/CloudFrontCookie.php file to look like:

Finally let’s add the CloudFrontCookie middleware to app/Http/Kernel.php under the middleware group web :

UPDATE — Ignore CloudFront Cookies from being encrypted

Under app/Http/Middleware/EncryptCookies.php make sure to ignore the CloudFront cookies from being encrypted.

Update Note — Just went through this process again and couldn’t figure out why the CloudFront cookies weren’t working. They needed to be ignored from laravels default cookie encryption.

Test it out!

Deploy your application to your production domain and give it a whirl.

If all the moving pieces are put together correctly when you open you application (in production only), in the console storage cookies tab you should see 3 new cookie files that will authenticate your requests to access your protected S3 resources on CloudFront!

Closing

I spent a ton of time banging my head and making little tweaks and googling to figure all this out. Hopefully this article will be some help if you are having trouble or would like to use CloudFront signed cookies in your application. Let me know if you see any errors or would like anything exampled more clearly. Happy Coding!

--

--

Steve Barbera

Full Stack Developer, Electronic music enthusiast, occasional tweeter. stevebarbera.com