Create a user delegation SAS token with Azure Blob Storage and JavaScript

This article shows you how to create a user delegation SAS token in the Azure Blob Storage client library v12 for JavaScript. A user delegation SAS, introduced with version 2018-11-09, is secured with Azure AD credentials and is supported for the Blob service only to:

  • Grant access to an existing container.
  • Grant access to create, use, and delete blobs.

To create a user delegation SAS, a client must have permissions to call the blobServiceClient.getUserDelegationKey operation. The key returned by this operation is used to sign the user delegation SAS. The security principal that calls this operation must be assigned an RBAC role that includes the Microsoft.Storage/storageAccounts/blobServices/generateUserDelegationKey/action.

The permissions granted to a client who possesses the SAS are the intersection of the permissions that were granted to the security principal that requested the user delegation key and the permissions that were granted to the resource on the SAS token in the signed permissions (sp) field. If a permission that's granted to the security principal via RBAC isn't also granted on the SAS token, that permission isn't granted to the client who attempts to use the SAS to access the resource.

The sample code snippets are available in GitHub as runnable Node.js files.

Package (npm) | Samples | API reference | Library source code | Give Feedback

Best practices for user delegation SAS tokens

Because anyone with the SAS token can use it to access the container and blobs, you should define the SAS token with the most restrictive permissions that still allow the token to complete the required tasks.

Best practices for SAS tokens

Use the DefaultAzureCredential in Azure Cloud

To authenticate to Azure, without secrets, set up managed identity. This allows your code to use DefaultAzureCredential.

To set up managed identity for the Azure cloud:

  • Create a managed identity
  • Set the appropriate Storage roles for the identity
  • Configure your Azure environment to work with your managed identity

When these two tasks are complete, use the DefaultAzureCredential instead of a connection string or account key. This allows all your environments to use the exact same source code without the issue of using secrets in source code.

Use the DefaultAzureCredential in local development

In your local development environment, your Azure identity (your personal or development account you use to sign in to Azure portal) needs to authenticate to Azure to use the same code in local and cloud runtimes.

Container: add required dependencies to your application

Include the required dependencies to create a container SAS token.

const {
    DefaultAzureCredential
} = require('@azure/identity');
const {
    ContainerClient,
    BlobServiceClient,
    ContainerSASPermissions,
    generateBlobSASQueryParameters,
    SASProtocol
} = require('@azure/storage-blob');

// used for local environment variables
require('dotenv').config();

Container: get environment variables

The Blob Storage account name and container name are the minimum required values to create a container SAS token:

// Get environment variables for DefaultAzureCredential
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

Create a SAS with DefaultAzureCredential

The following conceptual steps are required to create a SAS token with DefaultAzureCredential:

  • Set up DefaultAzureCredential
    • Local development - use personal identity and set roles for storage
    • Azure cloud - create managed identity
  • Use DefaultAzureCredential to get the user delegation key with UserDelegationKey
  • Use the user delegation key to construct the SAS token with the appropriate fields with generateBlobSASQueryParameters

Container: create SAS token with DefaultAzureCredential

With identity configured, use the following code to create User delegation SAS token for an existing account and container:

// Server creates User Delegation SAS Token for container
async function createContainerSas() {

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Best practice: create time limits
    const TEN_MINUTES = 10 * 60 * 1000;
    const NOW = new Date();

    // Best practice: set start time a little before current time to 
    // make sure any clock issues are avoided
    const TEN_MINUTES_BEFORE_NOW = new Date(NOW.valueOf() - TEN_MINUTES);
    const TEN_MINUTES_AFTER_NOW = new Date(NOW.valueOf() + TEN_MINUTES);

    // Best practice: use managed identity - DefaultAzureCredential
    const blobServiceClient = new BlobServiceClient(
        `https://${accountName}.blob.core.windows.net`,
        new DefaultAzureCredential()
      );

    // Best practice: delegation key is time-limited  
    // When using a user delegation key, container must already exist 
    const userDelegationKey = await blobServiceClient.getUserDelegationKey(
        TEN_MINUTES_BEFORE_NOW, 
        TEN_MINUTES_AFTER_NOW
    );

    // Need only list permission to list blobs 
    const containerPermissionsForAnonymousUser = "l";

    // Best practice: SAS options are time-limited
    const sasOptions = {
        containerName,                                           
        permissions: ContainerSASPermissions.parse(containerPermissionsForAnonymousUser), 
        protocol: SASProtocol.HttpsAndHttp,
        startsOn: TEN_MINUTES_BEFORE_NOW,
        expiresOn: TEN_MINUTES_AFTER_NOW
    };
 
    const sasToken = generateBlobSASQueryParameters(
        sasOptions,
        userDelegationKey,
        accountName 
    ).toString();

    return sasToken;
}

The preceding server code creates a flow of values in order to create the container SAS token:

Once you're created the container SAS token, you can provide it to the client that will consume the token. The client can then use it to list the blobs in a container. A client code example shows how to test the SAS as a consumer.

Container: use SAS token

Once the container SAS token is created, use the token. As an example of using the SAS token, you:

  • Construct a full URL including container name and query string. The query string is the SAS token.
  • Create a ContainerClient with the container URL.
  • Use the client: in this example, list blobs in the container with listBlobsFlat.
// Client or another process uses SAS token to use container
async function listBlobs(sasToken){

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;
    
    // Create Url
    // SAS token is the query string with typical `?` delimiter
    const sasUrl = `https://${accountName}.blob.core.windows.net/${containerName}?${sasToken}`;
    console.log(`\nContainerUrl = ${sasUrl}\n`);

    // Create container client from SAS token url
    const containerClient = new ContainerClient(sasUrl);

    let i = 1;

    // List blobs in container
    for await (const blob of containerClient.listBlobsFlat()) {
        console.log(`Blob ${i++}: ${blob.name}`);
    }    
}

Blob: add required dependencies to your application

Include the required dependencies to create n blob SAS token.

const {
    DefaultAzureCredential
} = require('@azure/identity');
const {
    BlockBlobClient,
    BlobServiceClient,
    BlobSASPermissions,
    generateBlobSASQueryParameters,
    SASProtocol
} = require('@azure/storage-blob');

// used for local environment variables
require('dotenv').config();

Blob: get environment variables

The Blob Storage account name and container name are the minimum required values to create a blob SAS token:

const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

When you need to create a blob SAS token, you need to have the blob name to create the SAS token. That will be predetermined such as a random blob name, a user-submitted blob name, or a name generated from your application.

// Create random blob name for text file
const blobName = `${(0|Math.random()*9e6).toString(36)}.txt`;

Blob: create SAS token with DefaultAzureCredential

With identity configured, use the following code to create User delegation SAS token for an existing account and container:

// Server creates User Delegation SAS Token for blob
async function createBlobSas(blobName) {

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Best practice: create time limits
    const TEN_MINUTES = 10 * 60 * 1000;
    const NOW = new Date();

    // Best practice: set start time a little before current time to 
    // make sure any clock issues are avoided
    const TEN_MINUTES_BEFORE_NOW = new Date(NOW.valueOf() - TEN_MINUTES);
    const TEN_MINUTES_AFTER_NOW = new Date(NOW.valueOf() + TEN_MINUTES);

    // Best practice: use managed identity - DefaultAzureCredential
    const blobServiceClient = new BlobServiceClient(
        `https://${accountName}.blob.core.windows.net`,
        new DefaultAzureCredential()
      );

    // Best practice: delegation key is time-limited  
    // When using a user delegation key, container must already exist 
    const userDelegationKey = await blobServiceClient.getUserDelegationKey(
        TEN_MINUTES_BEFORE_NOW, 
        TEN_MINUTES_AFTER_NOW
    );

    // Need only create/write permission to upload file
    const blobPermissionsForAnonymousUser = "cw"

    // Best practice: SAS options are time-limited
    const sasOptions = {
        blobName,
        containerName,                                           
        permissions: BlobSASPermissions.parse(blobPermissionsForAnonymousUser), 
        protocol: SASProtocol.HttpsAndHttp,
        startsOn: TEN_MINUTES_BEFORE_NOW,
        expiresOn: TEN_MINUTES_AFTER_NOW
    };
 
    const sasToken = generateBlobSASQueryParameters(
        sasOptions,
        userDelegationKey,
        accountName 
    ).toString();

    return sasToken;
}

The preceding code creates a flow of values in order to create the container SAS token:

Once you're created the blob SAS token, you can provide it to the client that will consume the token. The client can then use it to upload a blob. A client code example shows how to test the SAS as a consumer.

Blob: use SAS token

Once the blob SAS token is created, use the token. As an example of using the SAS token, you:

  • Construct a full URL including container name, blob name and query string. The query string is the SAS token.
  • Create a BlockBlobClient with the container URL.
  • Use the client: in this example, upload blob with upload.
// Client or another process uses SAS token to upload content to blob
async function uploadStringToBlob(blobName, sasToken, textAsString){

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Create Url SAS token as query string with typical `?` delimiter
    const sasUrl = `https://${accountName}.blob.core.windows.net/${containerName}/${blobName}?${sasToken}`;
    console.log(`\nBlobUrl = ${sasUrl}\n`);

    // Create blob client from SAS token url
    const blockBlobClient = new BlockBlobClient(sasUrl);

    // Upload string
    await blockBlobClient.upload(textAsString, textAsString.length, undefined);    
}

See also