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 |
CloudFrontPublicKey1: |
Managing key rotation in CloudFormation is possible but cumbersome. To add a new public key, duplicate the
CloudFrontPublicKey1
resource 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.
Looking for a new challenge?
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
private_key.pem
) - The Lambda function checks if the
Authorization
header contains the value Bearer secret. You likely want to replace this with something different to authenticate the user, e.g., by verifying a JWT.
LambdaEdgeFunctionRole: |
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:
CloudFrontCachePolicy: |
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:
curl https://DOMAIN_NAME/index.html |
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!