amazon-web-servicescookiesamazon-cloudfront

Access Denied Error when using Cloudfront with Signed Cookies


I know this topic has been asked and answered multiple times all over the internet but unfortunately I'm still having issues although trying basically every single related post out there!

My setup

I have an S3 bucket with video files for a frontend streaming service. The files get served via a CloudFront Distribution and in order to secure the file access, I have set up an API Gateway which redirect requests to a Custom Lambda to get my Signed Cookies for CloudFront.

However, tests for the individual services were all successful:

I want to emphasise, that S3, CloudFront, Lambda and the API Gateway are working individually but only as long as I don't attach the cookies to the request!

So my guess is that something is wrong with the signing of the cookies.

Custom Lambda

Since the lambda is the source of truth for cookie signing, I will show a minimal example of what I'm doing here. My lambda is written in Node JS and I'm using "@aws-sdk/cloudfront-signer": "^3.229.0",.

const url = `https://my-cloudfront.domian.com/path-to-src/*`;
const expTime = new Date(Date.now() + 5 * (60 * 60 * 1000)).toISOString();

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

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=domain.com; Path=/*`,
      `CloudFront-Signature=${signedCookie["CloudFront-Signature"]}; Domain=domain.com; Path=/*`,
      `CloudFront-Key-Pair-Id=${signedCookie["CloudFront-Key-Pair-Id"]}; Domain=domain.com; Path=/*`,
    ],
  },
};

callback(null, response);

This works, I get the 3 Set-Cookie headers which also get submitted when requesting the file. However, the result is always the same:

ACCESS DENIED

Tests

To test the individual services I did the following:

There obviously is a lot more important setup steps (e.g. bucket policies, custom domains, etc), which I did not mention here but can be posted on request if necessary.

This topic keeps me busy since a month now! Any help appreciated! 🙌


Solution

  • I found the solution and it's pretty embarrassing...

    My above example signs cookies via a Custom Policy but uses the Set-Cookie header keys from a Canned Policy. The difference in the docs is not really obvious but its clearly there...

    However, instead of

    Set-Cookie: CloudFront-Expires= ...
    

    you use

    Set-Cookie: CloudFront-Policy=<your policy string>
    

    The keys

    Set-Cookie: CloudFront-Expires= ...
    Set-Cookie: CloudFront-Signature= ...
    

    are identical.

    Hope this helps!