Serving content only to logged-in users with CloudFront Signed Cookies
This blog can be accessed by anyone with access to the free Internet. It’s a public website. But many websites offer a members-only area. You have to log in to get access to parts of the website. In this blog post, I demonstrate how CloudFront can be used to protect parts of your website from the public.
To serve content only to logged-in users with CloudFront, we have to wire three pieces together:
- A private & public key pair.
- A signed cookie protected CloudFront origin that uses the public key to verify signed cookies.
- A component that generates and returns signed cookies with the private key. We use Lambda@Edge here because CloudFront can trigger it directly.
I wired up the pieces using CloudFormation. Let’s have a look at the template step by step.
First, We define the S3 bucket that we use as origins for our CloudFront distribution. We use a single bucket here and separate the public and private files into folders. You could also use two S3 buckets if you wish, or you could use other origin types such as load balancers or external HTTP endpoints.
Second, we create the public key that CloudFront uses to verify the signature in the cookies. To generate a private/Public key pair, run the following commands:
openssl genrsa -out private_key.pem 2048
Managing key rotation in CloudFormation is possible but cumbersome. To add a new public key, duplicate the
CloudFrontPublicKey1resource and reference the new resource in the key group. Please don’t delete the old public key while it is in use. The expiry of your cookies (1 day in this example) defines the minimum wait duration.
Third, it’s time to implement the Lambda@Edge function that generates the signed cookies.
Please support our work!
We have published 327 articles, 41 podcast episodes, and 15 videos. It's all free and means a lot of work in our spare time.
Thanks to Alan Leech, Alex DeBrie, e9e4e5f0faef, Goran Opacic, jhoadley, Shawn Tolidano, Thorsten Hoeger, Todd Valentine, Vince Fulco, and all anonymous supporters for your help! We also want to thank all supporters who purchased a cloudonaut t-shirt. It gives us great pleasure to send our t-shirts all over the world.
With your help, we can continue to produce independent & high-quality content focused on AWS. Please support us!Support us
You might want to adjust the following:
MAX_AGE_IN_SECONDS: Expiry of the signed cookie (both the cookie and the content expires)
- Private key (from
- The Lambda function checks if the
Authorizationheader contains the value Bearer secret. You likely want to replace this with something different to authenticate the user, e.g., by verifying a JWT.
We use a custom policy here and not a canned policy. Canned policies only grant access to specific files, while custom policies can use the wildcard
*character to specify a larger group of files at once.
Last but not least, we define the CloudFront distribution:
To test the demo, create a CloudFormation stack in
us-east-1 based on the template. Once the stack is created, upload two files to the created S3 bucket:
aws s3 cp index.html s3://BUCKET_NAME/public/index.html
Now you can send requests against CloudFront. To get the public file:
To set up the cookies:
curl -c cookie.txt -I -X POST -H 'Authorization: Bearer secret' https://DOMAIN_NAME/cookie/
To get a private file with the cookies from the previous request:
curl -b cookie.txt https://DOMAIN_NAME/index.html
That’s all you need to run a public and private website behind CloudFront. I hope it helps!