node.js download file from aws s3 bucket via http request with AWS Signature Version 4

Main codes

function getYMD(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day;
    return r;
}

function getTZ(d) {
    let r = d.getFullYear().toString();
    let m = d.getUTCMonth() + 1;
    if (m < 10)
      m = "0" + m.toString();
    else
      m = m.toString();
    r = r + m;
    let day = d.getUTCDate();
    if (day < 10)
      day = "0" + day.toString();
    else
      day = day.toString();
    r = r + day + "T";
    let h = d.getUTCHours();
    if (h < 10)
      h = "0" + h.toString();
    else
      h = h.toString();
    r = r + h;
    let min = d.getUTCMinutes();
    if (min < 10)
      min = "0" + min.toString();
    else
      min = min.toString();
    r = r + min;
    let s = d.getUTCSeconds();
    if (s < 10)
      s = "0" + s.toString();
    else
      s = s.toString();
    r = r + s;
    r = r + "Z";
    return r;
}

const crypto = require('crypto');
function Hmac(key, string){
    const hmac = crypto.createHmac('sha256', key);
    hmac.end(string);
    return hmac.read();
}

function Signature(date, region, service, toSign) {
    let dateKey = Hmac('AWS4' + AWS_SECRETACCESS_KEY, date);
    let dateRegionKey = Hmac(dateKey, region);
    let dateRegionServiceKey = Hmac(dateRegionKey, service);
    let signingKey = Hmac(dateRegionServiceKey, 'aws4_request');
    let signature = Hmac(signingKey, toSign).toString('hex');
    return signature;
}

function stringToSign(timeStamp, scope, canonicalRequest) {
    var signParts = [];
    signParts.push('AWS4-HMAC-SHA256');
    signParts.push(timeStamp);
    signParts.push(scope);
    signParts.push(crypto.createHash('sha256').update(canonicalRequest).digest(
'hex'));
    var result = signParts.join('\n');
    console.log('string to sign');
    console.log(result);
    return result;
};

function canonicalRequest(method, uri, queryString, headers, signedHeaders, has
hedPayload) {
  var canonicalParts = [];
  canonicalParts.push(method);
  canonicalParts.push(uri);
  canonicalParts.push(queryString);
  canonicalParts.push(headers);
  canonicalParts.push(signedHeaders);
  canonicalParts.push(hashedPayload);
  var result = canonicalParts.join('\n');
  console.log('canonical request');
  console.log(result);
  return result;
}

function download(filepath) {
    let filename = filepath;
    let position = filename.lastIndexOf('/');
    if (position != -1) {
      filename = filename.substr(position+1);
    }
    let d = new Date();
    let dtz = getTZ(d);
    let dymd = getYMD(d);
    let scope = dymd + '/' + AWS_REGION + '/' + AWS_SERVICE + '/aws4_request';
    let uri = filepath;
    let queryString = "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + en
codeURIComponent(AWS_KEY + "/" + scope) + "&X-Amz-Date=" + dtz + "&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3Bfilena
me%3D" + filename;
    let headers = "host:s3.amazonaws.com";
    let signedHeaders = "";
    let hashedPayload = "host\nUNSIGNED-PAYLOAD";
    let request = canonicalRequest('GET', uri, queryString, headers, signedHead
ers, hashedPayload);
    let signature = Signature(dymd, AWS_REGION, AWS_SERVICE, stringToSign(dtz,
scope, request));
    let url = 'https://s3.amazonaws.com' + filepath + '?response-content-dispos
ition=attachment;filename=' + filename + '&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-A
mz-Credential=' + AWS_KEY + "/" + scope + '&X-Amz-Date=' + dtz + '&X-Amz-Expire
s=400&X-Amz-SignedHeaders=host&X-Amz-Signature=' + signature;
    console.log(url);
    var link = document.createElement('a');
    link.innerHTML = 'download';
    link.href = url;
    document.body.appendChild(link);
}

Usage

const AWS_KEY = 'your aws key';
const AWS_SECRETACCESS_KEY = 'your aws secret access key';
const AWS_REGION = 'your bucket region'; // default us-east-1
const AWS_SERVICE = 's3';

download('/yourbucket/objectkey');

examle

download

Refers

GET Object
Signing AWS Requests with Signature Version 4
Authenticating Requests: Using Query Parameters (AWS Signature Version 4)

Key point:
?response-content-disposition=attachment;filename=objectname
Using this header, browser will open an save dialog once the down load url is correct signed.

You will get a url link like:
https://s3.amazonaws.com/bucket/objectkey?response-content-disposition=attachment;filename=objectname&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJWWH7EGSUBWE34IQ/20181218/us-east-1/s3/aws4_request&X-Amz-Date=20181218T053905Z&X-Amz-Expires=400&X-Amz-SignedHeaders=host&X-Amz-Signature=71d82d4a99338d777bfa3517315d34cad94619a9ca891222808e16dd02de7835

Subscribe to Post, Code and Quiet Time.

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe