I am trying to generate AWS Cloudfront signed url using apex but every time get Access Denied.

public url is working fine. i.e. jueh890044563.cloudfront.net/test.pdf

But I need a signed url of cloudfront.

I have already done following things in AWS.

S3 bucket is private.

test.pdf is private.

Created cloudfront Distribution (domain name is : jueh890044563.cloudfront.net)

Apex code

String keyPairId = 'YTEHJLKFHHRKF';

Datetime now = DateTime.now();

Datetime expireson = now.addDays(2);

Long Lexpires = expireson.getTime()/1000;

String policyStatement = '{"Statement":[{"Resource":"jueh890044563.cloudfront.net/test.pdf","Condition":{"DateLessThan":{"AWS:EpochTime":'+Lexpires+'}}}]}';

policyStatement = EncodingUtil.base64Encode(Blob.valueOf(policyStatement));

String privateKey = 'dfgdfgtgdfgdfgdfsgdfgdfsgdfgdfgdfgsdf'+




Blob mac = Crypto.generateMac('HmacSHA1', blob.valueof(policyStatement),blob.valueof(privateKey)); 
String signed = EncodingUtil.base64Encode(mac);

String downloadUrl = 'jueh890044563.cloudfront.net/test.pdf?Policy='+policyStatement+'&Signature='+signed+'&Key-Pair-Id='+keyPairId;

Is downloadUrl is signedUrl? When we run this url then return Access Denied.

Thanks in advance.


I've got mine working like so:

private HTTPResponse signURL(String endpoint) {
    String keyPairId = 'keyPairIdFromCF';
    String secret    = 'secretFromS3';
    String method    = 'GET';
    Datetime dt      = Datetime.now();
    Long longTime    = dt.getTime();
    Long expiryLong  = (longTime / 1000) + 3600;
    String expiry    = String.valueOf(expiryLong);
    String policy    = policy('http://' + cfhost + '/' + endpoint, expiry);
    String policyEnc = EncodingUtil.base64Encode(Blob.valueOf(policy));
    String rPolicy   = cfReplace(policyEnc);
    Blob mac         = Crypto.generateMac('HMACSHA1',
    String signature = cfReplace(EncodingUtil.base64Encode(mac));

    HttpRequest req = new HttpRequest();
    req.setEndpoint('http://' + cfhost + '/' + endpoint + '?Policy=' +
        policyEnc + '&Signature=' + signature + '&Key-Pair-Id=' +

    Http http = new Http();
    HTTPResponse res = http.send(req);
    return res;

private String policy(String url, String expTime) {
    return '{"Statement":[{"Resource":'+ url + ',"Condition":{' +
           '"DateLessThan":{"AWS:EpochTime":'+ expTime + '}}}]}';

private String cfReplace(String s) {
    return s.replace('+', '-').replace('=', '_').replace('/', '~');

cfhost is obviously my Cloudfront domain. cfReplace is to change illegal characters as per the Cloudfront documentation. If you're still getting access denied, make sure your Cloudfront identity can both getObjects and listBuckets.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.