Skip to main content

Command Palette

Search for a command to run...

How to Securely Serve Private Content with CloudFront Signed URLs

Updated
9 min read

Need to share private content like paid videos or sensitive documents online, but worry about unauthorized access? Maybe you need time-limited links or access restricted only to specific users, and standard methods feel too insecure.

Amazon CloudFront Signed URLs solve this problem. They let you generate unique, temporary links that provide secure access to your private files. You control exactly who gets access and for how long, keeping your content safe. By the end, you’ll have a private S3 bucket hosting your content, a secure CloudFront distribution, a configured key pair for URL signing, and the ability to generate temporary signed URLs.

Prerequisites

Before you begin, make sure you have the following:

  • An AWS Account with appropriate permissions.

  • An S3 Bucket containing the private content you want to serve.

  • OpenSSL installed on your local machine.

  • Node.js installed locally.

Creating the S3 Bucket and Uploading the Content

You need to create an S3 bucket to store the content you want to serve if you don’t already have one.

  1. Log in to the AWS Management Console and navigate to S3.

  2. Click Create bucket.

  3. Enter a unique bucket name.

  4. Under Block Public Access settings for this bucket, ensure Block all public access is checked. This is the default and recommended setting for your private content.

  5. Leave other settings as default and click Create bucket.

  6. After the bucket has been successfully created, click on the bucket name to go into the bucket and click Upload.

  7. On the next page, click on Add files to select the file you would like to upload.

  8. After selecting the file, scroll down and click on Upload.

    If the upload was successful, you should see a message at the top of the page indicating that.

At this point, your file is securely stored but inaccessible from the internet, which is exactly what you want.

Create a CloudFront Distribution

Now, you'll create a CloudFront distribution to act as the secure gateway to your S3 content. You'll use Origin Access Control (OAC), the recommended method for granting CloudFront secure access to your private S3 bucket.

  1. In the AWS Management Console, navigate to CloudFront.

  2. On the CloudFront page, click Create a CloudFront distribution.

  3. Under Origin domain, select the S3 bucket you created earlier.

  4. For Origin access, select Origin access control settings (recommended).

  5. Click Create new OAC. You can accept the default name or provide your own, leave the signing behavior as default, and click Create.

  6. Under Web Application Firewall (WAF), select Do not enable security protections for this tutorial's simplicity.

  7. Leave other settings as default and click Create distribution.

    CloudFront will begin deploying your distribution, which can take several minutes. After clicking "Create distribution", CloudFront will display a banner with a suggested S3 bucket policy.

  8. Click Copy policy to copy the policy provided in the banner and click Go to S3 bucket permissions to update also on the banner to go to the bucket permissions.

    It will look something like this (your bucket name and distribution ID will vary):

     {
         "Version": "2008-10-17",
         "Id": "PolicyForCloudFrontPrivateContent",
         "Statement": [
             {
                 "Sid": "AllowCloudFrontServicePrincipal",
                 "Effect": "Allow",
                 "Principal": {
                     "Service": "cloudfront.amazonaws.com"
                 },
                 "Action": "s3:GetObject",
                 "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/*",
                 "Condition": {
                     "StringEquals": {
                         "AWS:SourceArn": "arn:aws:cloudfront::YOUR-ACCOUNT-ID:distribution/YOUR-DISTRIBUTION-ID"
                     }
                 }
             }
         ]
     }
    
  9. In the Permissions tab, scroll to the Bucket policy section and click Edit.

  10. Paste the copied policy into the editor and click Save Changes.

    This policy explicitly grants only your specific CloudFront distribution permission to retrieve objects (s3:GetObject) from your otherwise private S3 bucket.

    Once the distribution's status changes from "Deploying" to "Enabled", you can access your content through CloudFront, but it's not restricted by signed URLs yet. You could try accessing https://<YourDistributionDomainName>/<YourFileName> .

Create a Key Pair for Signing URLs

To create signed URLs, CloudFront needs a trusted public/private key pair. You'll upload the public key to AWS, associate it with your distribution via a Key Group, and keep the private key secure to generate the signatures yourself.

  1. Open a terminal or command prompt on your local machine and run the following to generate the private key:

     openssl genrsa -out private_key.pem 2048
    

    Then to generate the public key, run:

     openssl rsa -in private_key.pem -pubout -out public_key.pem
    

    These command create files named private_key.pem and public_key.pem in your current directory. You must guard the private_key.pem file carefully as anyone with this private key can generate signed URLs for your content.

  2. Now you need to upload the public key to CloudFront. Go back to the CloudFront console and on the left-hand navigation pane, under Key management, click Public keys.

  3. Next, click Create Public Key.

  4. Then enter a name, and paste the contents of your public_key.pem file (including the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- lines) into the Key field.

  5. Click Create public key. Make sure you note the ID assigned to this public key (you'll need it later for your signing script).

    You should get a success message at the top of the page if the key was created successfully.

  6. Next, you'll add this public key to a Key Group. In the left-hand navigation pane, under Key management, click Key groups.

  7. Next, click Create key group.

  8. Enter a name, select the public key you just added from the list and click Create Key Group.

This Key Group acts as a container for the public keys that CloudFront will trust for validating signed URLs for specific distribution behaviors you define.

Configure CloudFront to Require Signed URLs

Now, you'll tell CloudFront to actually enforce signed URL access for your content.

  1. Navigate back to your Distributions list in the CloudFront console.

  2. Click on the ID of the distribution you created earlier.

  3. Go to the Behaviors tab.

  4. Select the Default (*) behavior (or the specific path pattern you want to protect) and click Edit.

  5. Scroll down to the Restrict viewer access section, change the setting from No to Yes.

  6. For Trusted authorization type, select Trusted key groups (recommended).

  7. Under Trusted key groups, click Add key groups and select the Key Group you created in the previous.

  8. Click Save changes.

At this point, direct (unsigned) access to your content will be denied, and you’ll see an error if you try to load it without a signed URL. Try accessing your content again using the regular CloudFront URL (https://<YourDistributionDomainName>/<YourFileName>). This time, you should receive an error. Common errors include an XML response with AccessDenied or MissingKey. This confirms that CloudFront now requires a valid signed URL to grant access.

Generate a CloudFront Signed URL

The final step is for you to generate a signed URL using the private key you created. You'll typically do this programmatically within your application (e.g. when a logged-in authorized user of yours requests access to a file).

Here's an example using Node.js and the official AWS SDK v3 library for CloudFront signing:

  1. Install the necessary package on your local machine or server:

     npm install @aws-sdk/cloudfront-signer
    
  2. Create a signing script (e.g. generate-signed-url.js) and paste the following code:

     javascriptCopyEditconst { getSignedUrl } = require("@aws-sdk/cloudfront-signer");
    
     const cloudfrontDistributionDomain = "https://d3d35x2m0cjmbi.cloudfront.net";
     const s3ObjectKey = "index.html";
     const url = `${cloudfrontDistributionDomain}/${s3ObjectKey}`;
    
     const privateKey = `<privatekey>`;
     const keyPairId = "<keypair >";
     const futureDate = new Date();
     futureDate.setDate(futureDate.getDate() + 1); // Valid for the next day
     const dateLessThan = futureDate.toISOString().split("T")[0]; // Format as YYYY-MM-DD
    
     const signedUrl = getSignedUrl({
       url,
       keyPairId,
       dateLessThan,
       privateKey,
     });
    
     console.log("Signed URL:", signedUrl);
    
     const { getSignedUrl } = require("@aws-sdk/cloudfront-signer");
     const fs = require('fs');
    
     // --- Configuration ---
     // !! Replace these placeholders with your actual values !!
     const cloudfrontDistributionDomain = "https://dXXXXXXXXXXXXX.cloudfront.net"; // Your CloudFront distribution domain name
     const s3ObjectKey = "index.html"; // The key (path/filename) of your object in S3
     const privateKeyPath = "./private_key.pem"; // Path to your private key file
     const keyPairId = "KXXXXXXXXXXXX"; // The Public Key ID from CloudFront
     const validityDays = 1; // How many days the URL should be valid for
     // --- End Configuration ---
    
     // Construct the full URL to the object via CloudFront
     const url = `${cloudfrontDistributionDomain}/${s3ObjectKey}`;
    
     // Read the private key content
     let privateKey;
     try {
         privateKey = fs.readFileSync(privateKeyPath, 'utf8');
     } catch (err) {
         console.error("Error reading private key file:", err);
         process.exit(1); // Exit if you can't read the key
     }
    
     // Calculate the expiration date
     const futureDate = new Date();
     futureDate.setDate(futureDate.getDate() + validityDays);
     // CloudFront requires expiration date in 'YYYY-MM-DD' format for Canned Policies
     // Or provide full ISO string 'YYYY-MM-DDTHH:mm:ssZ' for Custom Policies (more complex)
     // Canned Policy is used here:
     const dateLessThan = futureDate.toISOString().split("T")[0];
    
     console.log(`Generating signed URL for: ${url}`);
     console.log(`Using Key Pair ID: ${keyPairId}`);
     console.log(`Valid until (exclusive): ${dateLessThan}`);
    
     // Generate the signed URL using a Canned Policy
     try {
         const signedUrl = getSignedUrl({
             url: url,           // The URL of the resource
             keyPairId: keyPairId,   // The ID of the CloudFront public key you uploaded
             dateLessThan: dateLessThan, // Expiration date (exclusive) in YYYY-MM-DD format
             privateKey: privateKey, // Your private key content read from the file
         });
    
         console.log("\nSigned URL:\n", signedUrl);
         console.log("\nPaste this URL into your browser to access the content.");
    
     } catch (error) {
         console.error("\nError generating signed URL:", error);
     }
    

    Update cloudfrontDistributionDomain with your actual CloudFront domain name, s3ObjectKey with the exact path and filename of your object in S3. Ensure privateKeyPath points correctly to where you saved your private_key.pem file and keyPairId with the Public Key ID you noted down when you uploaded the public key to CloudFront.

  3. Run the script from your terminal:

     node generate-signed-url.js
    

    The script will output a long URL containing query parameters like Expires, Signature, and Key-Pair-Id.

    The URL for your S3 object (e.g., index.html) is formed by concatenating your CloudFront distribution domain and the object key. Your private key and key pair ID are used to create a signature. The dateLessThan parameter sets an expiration date for the URL.

    The AWS SDK function getSignedUrl takes these inputs and returns a URL that includes your signature. Only someone with this URL and before it expires, can access your content.

  4. Copy the entire generated signed URL and paste it into your browser's address bar. You should now be able to access and view your private content. If you try the same URL after the expiration date (dateLessThan), you'll find that access is denied again.

Conclusion

You've now successfully configured CloudFront Signed URLs to protect content in your private S3 bucket. By leveraging Origin Access Control (OAC) and Trusted Key Groups, you've established a secure path where CloudFront can access your S3 objects using time-limited, cryptographically signed URLs that you generate. This method gives you granular control over who accesses your premium content, sensitive documents, or other private assets, ensuring they remain confidential and are only available under the conditions you define.

To further enhance your setup, you could explore using CloudFront Signed Cookies, which are often better suited if you need to provide access to multiple restricted files without generating unique URLs for each.

29 views