3
votes

Ideally, I would like to access all containers and blobs in storage. The Account SAS token is generated server side in my code. The client will call the API I created in Node.js to receive it. I see that you can create a SAS token manually using Azure Shell, but I prefer to have it generated serve-side since authentication will be implemented.

Following the account SAS generation documentation, it states that the string-to-sign should be constructed like this.

StringToSign = accountname + "\n" +  
    signedpermissions + "\n" +  
    signedservice + "\n" +  
    signedresourcetype + "\n" +  
    signedstart + "\n" +  // optional
    signedexpiry + "\n" +  
    signedIP + "\n" +  // optional
    signedProtocol + "\n" +  // optional
    signedversion + "\n" 

Example Token from documentation (broken up into multiple lines for better visibility):

sv=2019-02-02&ss=bf&srt=s&st=2019-08-01T22%3A18%3A26Z
&se=2019-08-10T02%3A23%3A26Z&sr=b&sp=rw
&sip=168.1.5.60-168.1.5.70&spr=https
&sig=F%6GRVAZ5Cdj2Pw4tgU7IlSTkWgn7bUkkAg8P6HESXwmf%4B

Token generated from Azure Shell:

se=2019-11-15&sp=rwdlac&sv=2018-03-28&ss=b&srt=sco&sig=<hidden signature>

What is odd is that in the example token, signedversion (sv) is provided first versus signedexpiry (se) in the token from Azure Shell.

Below is the code used to generate the token. I attempted to follow the same order as the token from Azure Shell:

app.get('/sas-token', (req, res, next) => {

  // const start = new Date(new Date().getTime() - (15 * 60 * 1000));
  const end = new Date(new Date().getTime() + (30 * 60 * 1000));

  const signedpermissions = 'rwdlac';
  const signedversion = '2018-03-28';
  const signedservice = 'b';
  const signedresourcetype = 'sco';
  // const signedstart = truncateIsoDate(start);
  const signedexpiry = truncateIsoDate(end);
  // const signedIP = '';
  const signedProtocol = 'https';

  const StringToSign = STORAGE_ACCOUNT_NAME + "\n" +
      signedpermissions + "\n" +
      signedservice + "\n" +
      signedresourcetype + "\n" +
      // signedstart + "\n" +  
      signedexpiry + "\n" +
      // signedIP + "\n" +  
      signedProtocol + "\n" +
      signedversion + "\n"

  const key = new Buffer(ACCOUNT_ACCESS_KEY, 'base64');
  let sig = crypto.createHmac('sha256', key).update(StringToSign, 'utf8').digest('base64');

  let sas =
    `se=${signedexpiry}
     &sp=${(signedpermissions)}
     &sv=${(signedversion)}
     &ss=${(signedservice)}
     &srt=${(signedresourcetype)}
     &sig=${encodeURIComponent(sig)}`;

  res.json({
      storageUri: STORAGE_ACCOUNT_NAME,
      storageAccessToken: sas
  });
});

When my client finally makes the request with the generated SAS token, I receive an error:

403 (Server failed to authenticate the request. 
Make sure the value of Authorization header is formed correctly including the signature.)

Is it possible to generate an Account SAS token for Blob storage in Node.js?

2

2 Answers

7
votes

According to my research, we can use Azure storage SDK azure-storage. For more details, please refer to https://github.com/Azure/azure-storage-node/blob/0557d02cd2116046db1a2d7fc61a74aa28c8b557/test/accountsas-tests.js.

var storage = require("azure-storage")
var startDate = new Date();
    var expiryDate = new Date();
    startDate.setTime(startDate.getTime() - 5*60*1000);
    expiryDate.setTime(expiryDate.getTime() + 24*60*60*1000);
    var AccountSasConstants = storage.Constants.AccountSasConstants;
    var sharedAccessPolicy = {
      AccessPolicy: {
        Services: AccountSasConstants.Services.BLOB ,
        ResourceTypes: AccountSasConstants.Resources.SERVICE + 
                       AccountSasConstants.Resources.CONTAINER +
                       AccountSasConstants.Resources.OBJECT,
        Permissions: AccountSasConstants.Permissions.READ + 
                     AccountSasConstants.Permissions.ADD +
                     AccountSasConstants.Permissions.CREATE +
                     AccountSasConstants.Permissions.WRITE +
                     AccountSasConstants.Permissions.DELETE +
                     AccountSasConstants.Permissions.LIST,
        Protocols: AccountSasConstants.Protocols.HTTPSORHTTP,
        Start: startDate,
        Expiry: expiryDate
      }
      
    };
    const accountname ="blobstorage0516";
    const key = "";
    var sas =storage.generateAccountSharedAccessSignature(accountname,key,sharedAccessPolicy);
    console.log(sas);

Besides, if you do not want to use the SDK to generate SAS token, please note that we cannot Omit \n if we do not use one property in the StringToSign and the properties we provide in StringToSign should be used when we create sas token. For example

const accountname ="blobstorage0516";
    const key = "";

    const start = new Date(new Date().getTime() - (15 * 60 * 1000));
        const end = new Date(new Date().getTime() + (30 * 60 * 1000));
    const signedpermissions = 'rwdlac';
        const signedservice = 'b';
        const signedresourcetype = 'sco';
        const signedexpiry = end.toISOString().substring(0, end.toISOString().lastIndexOf('.')) + 'Z';
        const signedProtocol = 'https';
        const signedversion = '2018-03-28';

        const StringToSign =
            accountname + '\n' +
            signedpermissions + '\n' +
            signedservice + '\n' +
            signedresourcetype + '\n' +
             '\n' +
            signedexpiry + '\n' +
             '\n' +
            signedProtocol + '\n' +
      signedversion + '\n';
      
    let sig = crypto.createHmac('sha256', Buffer.from(key, 'base64')).update(StringToSign, 'utf8').digest('base64');
  
    let sasToken =
      `sv=${(signedversion)}&ss=${(signedservice)}&srt=${(signedresourcetype)}&sp=${(signedpermissions)}&se=${encodeURIComponent(signedexpiry)}&spr=${(signedProtocol)}&sig=${encodeURIComponent(sig)}`;
    console.log(sasToken)
    var storageBlobEndpoint = "https://"+ accountname +".blob.core.windows.net"
    var container="blobcontainer";
    var blobName="CP4.png";
    var requestURL = storageBlobEndpoint + "/" + container + "/" + blobName +"?"+sasToken
    console.log(requestURL)

enter image description here enter image description here


Update

According to my test, we can use the sas token created by above code to do smoe actions on Azure blob storage. For example

1. List Container

Get https://<your account>.blob.core.windows.net/?comp=list+ "&"+"<your sas token>"

enter image description here enter image description here

2. get container Properties

Get https://<your account>.blob.core.windows.net/<your container>?restype=container&comp=list +"&" +"<your sas token>"

enter image description here Besides, regarding how to use the new sdk @azure/storage-blobto create SAS token, please refer to the following code

const accountname ="blobstorage0516";
    const key = "";
    const signedpermissions = 'rwdlac';
        const signedservice = 'b';
        const signedresourcetype = 'sco';
        
        const signedProtocol = 'https';
        const signedversion = '2018-03-28';
    const cerds = new storage.StorageSharedKeyCredential(accountname,key);
    var startDate = new Date();
    var expiryDate = new Date();
    startDate.setTime(startDate.getTime() - 5*60*1000);
    expiryDate.setTime(expiryDate.getTime() + 24*60*60*1000);
    expiryDate.setTime(expiryDate.getTime() + 24*60*60*1000);
    var result = storage.generateAccountSASQueryParameters({
      expiresOn : expiryDate,
      permissions: storage.AccountSASPermissions.parse(signedpermissions),
      protocol: storage.SASProtocol.Https,
      resourceTypes: storage.AccountSASResourceTypes.parse(signedresourcetype).toString(),
      services: storage.AccountSASServices.parse(signedservice).toString(),
      startsOn: startDate,
      version:signedversion

    },cerds).toString();
    console.log(result);
    
0
votes

Try with @azure/storage-blob npm package

const sasString = generateAccountSASQueryParameters({ ... }, sharedKeyCredential).toString()

See https://azuresdkdocs.blob.core.windows.net/$web/javascript/azure-storage-blob/12.0.0/globals.html#generateaccountsasqueryparameters