**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!