Amazon CloudFront is a Global Content Distribution Network. Essentially, this is a global caching system that delivers your website and app content really fast and low latency to your end users.

This is one of my favourite AWS services because you can increase the responsiveness, resiliency and scalability with minimal effort or cost – without even re-configuring your web servers!

Today, I’ll show you how to leverage some of the advanced protection functionality that is available within CloudFront.

The Use Case

You may want to protect content for your paying users vs users who are just browsing your website or protecting content for viewing only by your organisation.

There are few ways to implement the restriction of content using AWS services. These include:

  • CloudFront Signed URLs
  • CloudFront Signed Cookies
  • S3 Pre-signed URLs

S3 Pre-signed URLs are generally easier and faster to implement, but CloudFront Cookies and Signed URLs are more flexible and cater for scale.

In what situation should you use CloudFront Signing instead of S3 Pre-Signed URLs?

We recently worked on a project where we needed to overlay some images over the top of a Google Map. These images are split into geographic co-ordinates and represent a ’tile’ or slice that covers a particular area on the Google Map.

We could use pre-signed S3 URLs when requesting this content however, S3 pre-signed URLs only target one specific object. This is a problem because when we’re requesting 50+ image slices, we don’t want to call the AWS API to sign an S3 Object in a loop to sign every single image. That would take too long! And it would be very inefficient (not to mention that we’d probably get rate limited if several people accessed the content at the same time).

CloudFront Signed Cookies

Signed Cookies are perfect to use in a situation where you’re wanting to protect multiple resources in the same domain.

CloudFront Signed URLs

CloudFront Signed URLs are similar to S3 Pre-signed URLs except they are not Pre-signed and are instead evaluated during the request.

How to implement CloudFront Signed Cookies using .NET

Setting up your S3 Bucket and CloudFront

Log into your AWS account using your Root account, and add a CloudFront KeyPair using the Security Credentials page. (Unfortunately logging in as root is a requirement at this time).

You won’t see the ‘My Security Credentials’ link if you log in with an IAM User.

Click your Login Context menu and choose 'My Security Credentials'

Click your Login Context menu and choose ‘My Security Credentials’

Open the CloudFront Key pairs from the accordion and choose the Create New Key Pair button.

Click Create New KeyPair

Once you have created a new key pair, download the key pairs. Ensure you download both the Public and Private keys. Once you’ve navigated away from this screen, you will be unable to retrieve the keys for this Access Key.

Key Pairs Created

Create an S3 Bucket and CloudFront distribution

Create an S3 Bucket and add a CORS (Cross-Origin Resource) configuration policy. This allows CloudFront to pass through the Origin.

Add S3 CORS configuration

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>https://www.idea11.com.au</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<ExposeHeader>Access-Control-Allow-Origin</ExposeHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Create a CloudFront Distribution

Create a CloudFront distribution with the settings that make sense for your application. I’ve set up a custom SSL certificate and added Route53 ALIAS for my CloudFront distribution to protectedcontent.idea11.com.au.

Ensure that we’re whitelisting CORS related headers. It is likely that you’ll want the following headers whitelisted.

The most important option is to enable ‘Restrict Viewer Access’ – because we want to use signed cookies.

Click Deploy and wait until your CloudFront web distribution configuration has replicated.

You should now have a CloudFront distribution which uses S3 as an origin.

Next we’ll modify our application to sign cookies.

Modifying your .NET app to handle Cookie Signing

The CloudFront documentation is a little bit out of date at the moment – it’s not required to convert your PEM key to the crazy XML format. The SDK now does this for you automatically.

In this code example, I’m using the ASP.NET Core web app template with OpenID Connect – I’ll show how to use OpenID connect with AWS Cognito in a future blog post.

At a minimum, we need a way to provide the login functionality and to confirm who your users are. I am assuming that you have your own login system. In this post, I’m using the OpenID Connect extension on the ASP.NET Core MVC Authentication Middleware.

At this point, you’ll have a CloudFront distribution already configured, with an origin pointing to an S3 Bucket.

  1. Install the AWSSDK.CloudFront package and import the Amazon.CloudFront namespace into your class
  2. Within your login logic, call GetCookiesForCustomPolicy, and pass in the required parameters (example below)
  3. Append the generated CloudFront cookie values (Signature, Policy and KeyPair) to the Response as Cookies

 

var signedCookiePolicy = "https://protectedcontent.idea11.com.au/*";
var cookieDomain = ".idea11.com.au";
var cloudFrontPrivateKey = "<PRIVATE_KEY_FROM_SECURITY_CREDENTIALS_PAGE>";
var cloudFrontKeyId = "<KEY_ID_FROM_SECURITY_CREDENTIALS_PAGE>";
var expiryTime = DateTime.UtcNow.AddDays(3); //We want to provide access to the resources for three days
var availableTime = DateTime.UtcNow; //We want to provide access from now
options.Events = new OpenIdConnectEvents
{
   OnUserInformationReceived = (context) =>
   {
      //we want to generate cookie values
      var cookies = AmazonCloudFrontCookieSigner.GetCookiesForCustomPolicy(signedCookiePolicy, new StringReader(cloudFrontPrivateKey), cloudFrontKeyId, expiryTime, availableTime, "0.0.0.0/0");

      var cookieOptions = new CookieOptions
      {
         Domain = cookieDomain,
         Secure = true,
         HttpOnly = true,
      };

      //we'll append the cookies to our Response
      context.Response.Cookies.Append(cookies.Policy.Key, cookies.Policy.Value, cookieOptions);
      context.Response.Cookies.Append(cookies.KeyPairId.Key, cookies.KeyPairId.Value, cookieOptions);
      context.Response.Cookies.Append(cookies.Signature.Key, cookies.Signature.Value, cookieOptions);

      return Task.CompletedTask;
   }
}

Once we’ve logged in, three cookies (Signature, KeyPair and Policy) will be set and passed back to the browser, which will be applied to the domain “.idea11.com.au”.

When we access any content provided by the CloudFront distribution (for example: https://protectedcontent.idea11.com.au/paidviewersonly.mp4), the cookies that we set for “.idea11.com.au” will be passed by the browser to the subdomain to successfully authorise the request.

 

Considerations for using Signed Cookies

You may want to consider how you lay out your application resources across domains and CloudFront distributions.

Cookies will be made available to subdomains, if they are applied at a top level.

For example: Setting a cookie at .idea11.com.au, will make the cookie available to protectedcontent.idea11.com.au

Potential Gotchas!

CloudFront runs in UTC

CloudFront is always in UTC time, so make sure that your application logic considers timezones.

Cookies are cached by the browser

The nature of cookies mean that it is to be expected that they will be cached by the browser. This is by design.

If you’re debugging access, ensure that you’ve disabled your browsers cache, otherwise you might try modifying application logic, but your cookies are still allowing access.