Signing AWS CloudFront requests with Laravel

Published under Laravel

If you upload files to AWS S3 via your Laravel application, but want to restrict access to those files, you can do so using signed requests.

Here's what the flow looks like:

For most use cases, it makes sense to put a CloudFront distribution in front of your S3 bucket which will distribute your files to various edge locations across the world. We'll walk through how we can setup a S3 bucket and CloudFront distribution with access available only through signed requests.

This article will assume you have the following already configured:

Update CloudFront origin's settings

The first thing we need to do is update the CloudFront origin settings to allow access to the S3 bucket:

Update CloudFront behaviour settings

Next, we need to tell the CloudFront distribution to accept signed requests.

Verify the S3 bucket's policy was updated

In the first step, we selected "Yes, Update Bucket Policy". However, we should verify the policy was updated correctly:

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity XXXXXXX"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::XXXXXXXX/*"
        }
    ]
}

Alright, that's all we need to do on the AWS side, now onto updating your application.

Set ENV variable for AWS_URL

We need to tell our application where it can find our CloudFront distribution. Update your .env file to include a variable for AWS_URL. The value should be the domain name of your CloudFront distribution:

AWS_URL=https://XXXXX.cloudfront.net

Install package to sign requests

There's an awesome package available on GitHub which takes care of all the heavy lifting: https://github.com/dreamonkey/laravel-cloudfront-url-signer

Install the package by running:

composer require dreamonkey/laravel-cloudfront-url-signer

Update your controller's logic to return a signed request

$cloudfrontUrl = config('filesystems.disks.s3.url') . '/' . $filepath;
$signedUrl = CloudFrontUrlSigner::sign($cloudfrontUrl);

// You can either return the signed URL as string...
// return $signedUrl;

// or redirect the user to the file via the signed URL
return redirect($signedUrl);

By default, the CloudFrontUrlSigner::sign method will generate a signed URL that is valid for 1 day. Check out the GitHub page to see the various options you can pass to the sign method: https://github.com/dreamonkey/laravel-cloudfront-url-signer