Signed Cookies for Cloudfront not working

0

Since this is keeping me busy since 3 months now, my last hope is this forum before I give up trying to solve this via AWS.

Problem description

I have a couple of S3 buckets with files I want to distribute to my frontend application via Cloudfront. To keep things simple, I try with only two buckets at the moment:

  • a video bucket, which is supposed to stream video files via HLS
  • an asset bucket for all sorts of assets which are used in the frontend application.

Since one requirement is streaming videos - and thus I need to allow access to an entire folder on the bucket - I need to use signed cookies to do so. To retrieve my signed cookies, I have created an API Gateway which allows to set custom headers. However, this all seems to work fine as long as I do not try to use the cookies! As soon as they come into play I receive an Access Denied error.

The set-up

Over time I have followed quite a number of tutorials (official and unofficial) and therefore I skip listing all the links.

S3

I have set the permissions to work with my Cloudfront Distribution

{
  "Sid": "AllowCloudFrontServicePrincipalReadOnly",
  "Effect": "Allow",
  "Principal": {
    "Service": "cloudfront.amazonaws.com"
  },
   "Action": "s3:GetObject",
   "Resource": "arn:aws:s3:::XXX/*",
   "Condition": {
   "StringEquals": {
     "AWS:SourceArn": "arn:aws:cloudfront::XXX:distribution/XXX"
    }
  }
}

Furthermore, currently the access is set to public on this bucket for testing reasons.

Cloudfront

  • I have set up a Cloudfront distribution with a custom SSL certificate to avoid CORS issues.
  • I've also created an origin group which include the two buckets from earlier.
  • The default behaviour restricts viewer access via public ssh key.
  • The Cache key and origin requests are set to custom policies to make sure we allow the required headers for CORS and cookies.
Access-Control-Request-Method
Access-Control-Allow-Origin
Access-Control-Request-Headers

I get access to my files without any errors if I disable the viewer access via PUB key. So I'm pretty sure there is something wrong with my cookies.

API Gateway

  • I have set up an API gateway with custom domain to avoid CORS issues.
  • I have a proxy which redirects the request to a custom lambda, which again signs the cookies and returns the Set-Cookie header.

This all works fine, I get the cookies returned and they get send with the (file) request if available.

Custom lambda

So far, it's a very easy lambda written in Node.js which signed the cookies for a requested file. I'm using the getSignedCookies method from the Cloudfront Signer V3. Cookies are signed like this:

const signedCookie = getSignedCookies({
    keyPairId: awsCloudfrontKeyPairId,
    privateKey: awsCloudfrontPrivateKey,
    url: url,
    dateLessThan: getExpTime,
});

This is how I return the cookie headers:

const response = {
      statusCode: 200,
      isBase64Encoded: false,
      body: JSON.stringify({ url: url, bucket: bucket, key: key }),
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "https://my-custom-forntend-domain.com",
        "Access-Control-Allow-Credentials": true,
        "Access-Control-Allow-Headers": "Content-Type",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
      },
      multiValueHeaders: {
        "Set-Cookie": [
          `CloudFront-Expires=${signedCookie["CloudFront-Expires"]}; Domain=my-custom-forntend-domain.com; Path=/; Max-Age=86400`,
          `CloudFront-Signature=${signedCookie["CloudFront-Signature"]}; Domain=my-custom-forntend-domain.com; Path=/; Max-Age=86400`,
          `CloudFront-Key-Pair-Id=${signedCookie["CloudFront-Key-Pair-Id"]}; Domain=my-custom-forntend-domain.com; Path=/; Max-Age=86400`,
        ],
      },
    };

Like mentioned earlier, this works and the cookies get submitted on the file request. I just end up wit getting the Access Denied error every single time.

Any help appreciated! 🙌

2 Answers
0

You may need to verify the Domain and Path attributes in Set-cookie response headers.

CloudFront returns a 403 Access Denied error if cookies are returned from CloudFront but weren't included in following requests to the same domain. In this case, check the cookie attributes Domain and Path in the Set-Cookie response header.

The Domain value is the domain name for the requested file. The Path value is the path for the requested file. *To use an alternate domain name (such as example.com) in URLs, add an alternate domain name to your distribution.

JED007
answered a year ago
0

Hi @JED007,

thanks for your response!

I have had time to play around with this but unfortunately with no luck. Just to be clear, I'm signing the cookie like so:

const signedCookie = getSignedCookies({
      keyPairId: awsCloudfrontKeyPairId,
      privateKey: awsCloudfrontPrivateKey,
      url: url,
      dateLessThan: getExpTime,
});

where url=https://cloudfront.custom-domain.com/path-to-file/*.

The lambda response is set to:

const response = {
      statusCode: 200,
      isBase64Encoded: false,
      body: JSON.stringify({ url: url, bucket: bucket, key: key }),
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Credentials": true,
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET",
      },
      multiValueHeaders: {
        "Set-Cookie": [
          `CloudFront-Expires=${signedCookie["CloudFront-Expires"]}; Domain=cloudfront.custom-domain.com; Path=/`,
          `CloudFront-Signature=${signedCookie["CloudFront-Signature"]}; Domain=cloudfront.custom-domain.com; Path=/`,
          `CloudFront-Key-Pair-Id=${signedCookie["CloudFront-Key-Pair-Id"]}; Domain=cloudfront.custom-domain.com; Path=/`,
        ],
      },
};

If I then request a file from https://cloudfront.custom-domain.com/path-to-file/video.m3u8, the cookies are sent with the request but resulting in the 403 error. However, if I set Path=/path-to-file/*, no cookies get sent on the request and I get the Missing Key error.

FloRag
answered a year ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions