{"id":1815,"date":"2015-11-06T00:00:00","date_gmt":"2015-11-05T23:00:00","guid":{"rendered":"https:\/\/wwwneu.strehle.de\/tim\/weblog\/archives\/2015\/11\/06\/1575-2\/"},"modified":"2025-07-31T21:59:25","modified_gmt":"2025-07-31T19:59:25","slug":"1575-2","status":"publish","type":"post","link":"https:\/\/www.strehle.de\/tim\/weblog\/archives\/2015\/11\/06\/1575-2\/","title":{"rendered":"Using CloudFront Signed Cookies with the AWS SDK for PHP"},"content":{"rendered":"\n<p>If you store files on Amazon AWS S3 and want to restrict access to them, you can do so by setting up an <a href=\"http:\/\/docs.aws.amazon.com\/AmazonCloudFront\/latest\/DeveloperGuide\/PrivateContent.html\">AWS CloudFront distribution<\/a> with either <a href=\"http:\/\/docs.aws.amazon.com\/AmazonCloudFront\/latest\/DeveloperGuide\/private-content-choosing-signed-urls-cookies.html\">Signed URLs or Signed Cookies<\/a>. Signed Cookies are interesting if you want to use static file URLs and just make sure only people logged into your application can access the files. (But think twice which approach to use; I ended up going back to Signed URLs because I cannot set cookies in each scenario.)<\/p>\n\n\n\n<p>The setup can look like this \u2013 note the use of a custom subdomain (a CNAME in your DNS) to access CloudFront to make sure the application can set cookies that reach CloudFront (using HTTPS with a custom subdomain requires extra hoops, by the way) \u2013 click to enlarge:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/DC-X_AWS_S3_CloudFront_Integration.jpg\"><img decoding=\"async\" src=\"https:\/\/s3.eu-central-1.amazonaws.com\/files.strehle.de\/tim\/blog\/DC-X_AWS_S3_CloudFront_Integration.jpg\" alt=\"\"\/><\/a><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>With the help of the <a href=\"http:\/\/docs.aws.amazon.com\/AmazonCloudFront\/latest\/DeveloperGuide\/private-content-signed-cookies.html\">CloudFront documentation on Signed Cookies<\/a>, Frederick Cheung\u2019s <a href=\"http:\/\/www.spacevatican.org\/2015\/5\/1\/using-cloudfront-signed-cookies\/\">Using Cloudfront Signed Cookies<\/a> and Markus Ebenhoeh\u2019s <a href=\"https:\/\/mnm.at\/markus\/2015\/04\/05\/serving-private-content-through-cloudfront-using-signed-cookies\/\">Serving Private Content Through CloudFront Using Signed Cookies<\/a>, I managed to make it work. Unlike that last article, I\u2019m using the official <a href=\"http:\/\/aws.amazon.com\/sdk-for-php\/\">AWS SDK for PHP<\/a>, and I had no problem using PHP\u2019s setcookie() function.<\/p>\n\n\n\n<p>Here\u2019s my code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\n\/\/ Settings\n\n$access_key_id = 'ABCDEFG';\n$secret_access_key = 'abcdefg123456';\n$region = 'eu-central-1';\n$key_pair_id = 'UVWXYZ';\n$private_key = '\/path\/to\/aws-cloudfront-pk-UVWXYZ.pem';\n$cloudfront_base_url = 'http:\/\/files.example.com\/basedir\/';\n$allowed_resource = $cloudfront_base_url . '*';\n$policy_expires_after = (86400 * 7); \/\/ 1 week\n\n\/\/ Initialize AWS SDK for PHP\n\nrequire_once '\/path\/to\/aws_sdk\/aws-autoloader.php';\n\n$sdk_params =\n&#91;\n    'region' =&gt; $region,\n    'version' =&gt; 'latest',\n    'credentials' =&gt; new \\Aws\\Credentials\\Credentials\n    (\n        $access_key_id, \n        $secret_access_key\n    )\n];\n\n$sdk = new \\Aws\\Sdk($sdk_params);        \n\n\/\/ CloudFront cookie #1: CloudFront-Key-Pair-Id\n\n$cookies = &#91; ];\n\n$cookies&#91; 'CloudFront-Key-Pair-Id' ] = $key_pair_id;\n\n\/\/ CloudFront cookie #2: CloudFront-Policy\n\n$policy =\n&#91;\n   'Statement' =&gt; \n   &#91;\n      &#91;\n         'Resource' =&gt; $allowed_resource,\n         'Condition' =&gt;\n         &#91;\n            'DateLessThan' =&gt; \n            &#91; \n                'AWS:EpochTime' =&gt; (time() + $policy_expires_after)\n            ]\n         ]\n      ]\n   ]\n];\n\n$policy = json_encode($policy);\n\n$cookies&#91; 'CloudFront-Policy' ] = base64_encode($policy);\n\n\/\/ CloudFront cookie #3: CloudFront-Signature\n\n$cloudfront = $sdk-&gt;createCloudFront();\n\n$url_signing_params =\n&#91;\n    'url' =&gt; $allowed_resource,\n    'policy' =&gt; $policy,\n    'key_pair_id' =&gt; $key_pair_id,\n    'private_key' =&gt; $private_key\n];\n\n$signed_url = $cloudfront-&gt;getSignedUrl($url_signing_params);\n\n\/\/ We just need the \"Signature\" query string part of the signed URL\n\nparse_str(parse_url($signed_url, PHP_URL_QUERY), $signed_url_arr);\n$signature = $signed_url_arr&#91; 'Signature' ];\n\n$cookies&#91; 'CloudFront-Signature' ] = $signature;\n\n\/\/ Set cookies\n\n\/\/ We're on app.example.com, files are on files.example.com\n\/\/ (a CNAME we set up in our DNS).\n\/\/ Set cookies for .example.com to that they're sent to the CloudFront\n\/\/ subdomain.\n\n\/\/ test.www.example.com =&gt; .example.com\n\/\/ XXX bug: www.example.com.au =&gt; .com.au, which you cannot set cookies for \n\n$host_parts = array_reverse\n(\n    explode('.', parse_url($cloudfront_base_url, PHP_URL_HOST))\n);\n\n$cookie_domain = sprintf('.%s.%s', $host_parts&#91; 1 ], $host_parts&#91; 0 ]);\n\n$cookie_path = parse_url($cloudfront_base_url, PHP_URL_PATH);\n\nforeach ($cookies as $cookie_name =&gt; $cookie_value)\n{\n    setcookie($cookie_name, $cookie_value, 0, $cookie_path, $cookie_domain);\n}\n\n\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>If you store files on Amazon AWS S3 and want to restrict access to them, you can do so by setting up an AWS CloudFront distribution with either Signed URLs or Signed Cookies. Signed Cookies are interesting if you want to use static file URLs and just make sure only people logged into your application [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_share_on_mastodon":"0"},"categories":[1],"tags":[],"class_list":["post-1815","post","type-post","status-publish","format-standard","hentry","category-weblog"],"share_on_mastodon":{"url":"","error":""},"_links":{"self":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1815","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/comments?post=1815"}],"version-history":[{"count":1,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1815\/revisions"}],"predecessor-version":[{"id":1918,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/posts\/1815\/revisions\/1918"}],"wp:attachment":[{"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/media?parent=1815"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/categories?post=1815"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.strehle.de\/tim\/wp-json\/wp\/v2\/tags?post=1815"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}