Skip to content

Cloudfront: Signed Cookies 403 - NextJs/CloudFlare

0

**CloudFront Signed Cookie Issue with CNAME, Next.js and Cloudflare ** Setup I'm experiencing an issue with AWS CloudFront signed cookies for protected content(s3) with the following setup:

Frontend/Backend: Next.js application serving from dev.example.com CDN: CloudFront distribution with custom domain privatecdn.example.com Storage: S3 bucket private-2024-example-com DNS: Cloudflare managing the domain with a CNAME for privatecdn.sooper.app pointing to CloudFront (in DNS-only mode - gray cloud) Authentication: Using CloudFront signed cookies with crossOrigin="use-credentials" on html video elements. Also tried fetch requests with native js and axios with credentials.

Issue: Video requests from my Next.js application are failing with 403 Access Denied errors from CloudFront/S3. The browser correctly makes requests to privatecdn.example.com, but CloudFront logs show the hostname as %randomstringyhere%.cloudfront.net. The policy in the signed cookies specifies https://privatecdn.example.com/* as the Resource, but the domain mismatch is causing validation failures. The cookies themselves are being passed correctly.

What I've tried and other notes

Confirmed the file exists in S3 at the correct path Set up proper CORS configuration with all necessary headers (I think), sharing setup here below! Confirmed CloudFront distribution has the correct CNAME setup Ensured S3 bucket policy allows CloudFront access (Version 2012-10-17) Verified the signed cookies have the correct format and haven't expired Testing direct requests with curl shows the same 403 errors!!

CloudFront Logs CloudFront logs consistently show requests going to afdtfaghdfahgfshfd-etc.cloudfront.net despite the browser making requests to privatecdn.example.com.

Any help resolving this domain mismatch issue with CloudFront signed cookies would be greatly appreciated.

For reference: I've followed this tutorial: https://youtu.be/CAep1sOzEHM?feature=shared&t=2876

Distrubution:

  • Origin access control settings (recommended)
  • Redirect HTTP to HTTPS
  • Restrict viewer access
  • Key Group & keys confirmed to be correct.

Cache policy and origin request policy

  • Cache policy: CachingDisabled request policy:
  • AllViewerExceptHostHeader (Tried them all, ended up with this option).

S3: Block public access: yes Policy: { "Version": "2012-10-17", "Id": "PolicyForCloudFrontPrivateContent", "Statement": [ { "Sid": "AllowCloudFrontServicePrincipal", "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::private-2024-xxy/", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::xxy:distribution/xxy" } } }, { "Sid": "2", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity xxy" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::private-2024-xxy/" } ] }

CORS:

[ { "AllowedHeaders": [ "Authorization", "Content-Type", "Accept", "Origin", "Referer", "User-Agent", "Cookie", "CloudFront-Key-Pair-Id", "CloudFront-Policy", "CloudFront-Signature", "X-Requested-With", "Access-Control-Request-Method", "Access-Control-Request-Headers" ], "AllowedMethods": [ "GET", "HEAD", "POST", "PUT", "DELETE" ], "AllowedOrigins": [ "http://localhost:3000", "http://localhost:8000", "https://sprtest.sooper.app", "https://demo.sooper.app", "https://dev.sooper.app", "https://sooper.app", "https://privatecdn.sooper.app" ], "ExposeHeaders": [ "ETag", "CloudFront-Policy", "CloudFront-Signature", "CloudFront-Key-Pair-Id", "Access-Control-Allow-Origin" ], "MaxAgeSeconds": 30 } ] //Tried all sort of settings.

Frontend request:

https://privatecdn.example.com/posts/pst-e3e425d2-6011-43c8-946e-2064ef381499/upload-1453cca7-12d9-42a1-84fc-ba4d806c650c.mp4 Request Method: GET Status Code: 403 Forbidden

access-control-allow-credentials:
true
access-control-allow-methods:
GET, HEAD, POST, PUT, DELETE
access-control-allow-origin:
https://dev.example.com
access-control-expose-headers:
ETag, CloudFront-Policy, CloudFront-Signature, CloudFront-Key-Pair-Id, Access-Control-Allow-Origin
access-control-max-age:
30
content-type:
application/xml
date:
Sun, 02 Mar 2025 14:27:38 GMT
referrer-policy:
strict-origin-when-cross-origin
server:
AmazonS3
strict-transport-security:
max-age=31536000
vary:
Origin, Access-Control-Request-Headers, Access-Control-Request-Method
via:
1.1 7xxy.cloudfront.net (CloudFront)
x-amz-cf-id:
DtfyehcYBoxrVYqnZEfwIWmd6rxhb19gfFDbn-KyDuX6uHv5s6Gu6A==
x-amz-cf-pop:
AMS1-P3
x-cache:
Error from cloudfront
x-content-type-options:
nosniff
x-frame-options:
SAMEORIGIN
x-xss-protection:
1; mode=block

'use server';

import { cookies } from 'next/headers';
import {getSignedCloudfrontCookies} from "@lib/s3/v3/getSignedCloudfrontCookie";

export async function setCloudfrontCookiesAction(key: string) {
    try {
        const signedCookies = await getSignedCloudfrontCookies(key);
        console.log({signedCookies})
        const cookieStore = await cookies();

        Object.entries(signedCookies).forEach(([key, value]) => {
            cookieStore.set({
                name: key,
                value: value as string,
                path: '/',
                secure: true,
                // httpOnly: true,
                sameSite: "none",
                domain: '.example.com', // Matches ?!
                // sameSite: 'none', // Same-site works here
            });
        });


        return { success: true, data: signedCookies };
    } catch (error) {
        console.error('Failed to sign cookies:', error);
        return { success: false, error: 'Failed to set cookies' };
    }
}
'server only';

import {getSignedCookies} from "@aws-sdk/cloudfront-signer";

export async function getSignedCloudfrontCookies(key: string, expiresIn = 3600) {
    try {
        const cloudfrontUrl = process.env.AWS_CLOUDFRONT_PRIVATE_URL;
        const keyPairId = process.env.AWS_CLOUDFRONT_PRIVATE_KEYPAIR_ID;
        const privateKey = process.env.AWS_CLOUDFRONT_PRIVATE_PK;

        if (!cloudfrontUrl || !keyPairId || !privateKey) {
            throw new Error("No CloudFront URL, keyPairId or privateKey provided");
        }

        const url = `https://privatecdn.example.com/*`;

        const policy = JSON.stringify({
            Statement: [
                {
                    Resource: url,
                 
                    Condition: {
                        DateLessThan: {
                            "AWS:EpochTime": Math.floor(new Date().getTime() / 1000) + 60 * 60 * 10 // 10 hours
                        }
                    }
                }
            ]
        });

        const plainKey = Buffer.from(privateKey, 'base64').toString('ascii');
        return getSignedCookies({
            keyPairId,
            privateKey: plainKey,
            policy,
        });
    } catch (e) {
        console.error("Error generating CloudFront signed cookies", e);
        return false;
    }
}

Note; I'm not actually using example.com, and xxy.

And help highly appreciated!

2 Answers
0

Above ai comment is inaccurate, if anyone got any pointers for me, highly appreciatedd

answered 2 years ago

-1

The issue you're experiencing with CloudFront signed cookies and 403 Access Denied errors is likely due to a mismatch between the domain in your cookie policy and the actual request domain. Here are some key points to address:

  1. Cookie Policy: Your policy specifies https://dev.example.com/* as the Resource, but requests are going to privatecdn.example.com. This mismatch is likely causing the validation failure. Update your policy to include the correct domain (privatecdn.example.com) or use a wildcard to cover both domains.

  2. CloudFront Distribution: Ensure that privatecdn.example.com is added as an Alternate Domain Name (CNAME) in your CloudFront distribution settings. This allows CloudFront to recognize requests coming to this domain.

  3. Origin Request Policy: You're using AllViewerExceptHostHeader, which might be causing issues. Try creating a custom policy that includes the Host header, as this can be crucial for proper routing.

  4. CORS Configuration: Your CORS settings look good, but make sure privatecdn.example.com is included in the AllowedOrigins list if it's not already there.

  5. Cookie Settings: In your setCloudfrontCookiesAction function, set the cookie domain to '.privatecdn.example.com' instead of '.example.com' to match the request domain.

  6. CloudFront Logs: The fact that logs show requests going to a cloudfront.net domain suggests there might be a redirect happening. Double-check your DNS settings in Cloudflare to ensure the CNAME is correctly pointing to your CloudFront distribution.

  7. S3 Bucket Policy: Your policy looks correct, but ensure the ARNs match your actual resources.

To resolve the issue:

  1. Update your signed cookie policy to include the correct domain.
  2. Verify CloudFront distribution settings, especially Alternate Domain Names.
  3. Adjust the cookie domain in your server-side code.
  4. Create a custom Origin Request Policy that includes the Host header.
  5. Double-check DNS settings in Cloudflare.

If the issue persists after making these changes, you may need to review your CloudFront distribution's behavior settings and ensure that the Origin Access Control (OAC) is correctly configured for your S3 bucket.

Sources
HTTP 403 status code (Permission Denied) - Amazon CloudFront
Cloudfron 403 error messsage | AWS re:Post
CloudFront with S3 as Origin, returns 403 "ACCESS DENIED ERROR" due to x-cache: error from cloudfront. | AWS re:Post
Cloudfront cannot access s3 bucket | AWS re:Post

answered 2 years 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.