Share blobs with user delegation SAS tokens

ยท 2 min read

A SAS token signed with the storage account key can't be revoked without rotating the key. A user delegation SAS is signed with Entra ID credentials, expires sooner and fits with managed identity.

Sooner or later an app needs to let someone download a file from Blob Storage without making the container public: an invoice PDF, an export, an uploaded document. The standard answer is a shared access signature (SAS): a URL with a signed token that grants limited access for a limited time.

How that token is signed matters more than most people realize.

Two kinds of SAS

  • Account key SAS (service SAS). Signed with the storage account's access key. To revoke a leaked token early, you have to rotate the key, which breaks everything else using it. It also means your app needs the key in the first place.
  • User delegation SAS. Signed with a user delegation key that your app obtains with its Entra ID identity, such as a managed identity. No account key involved, the delegation key is valid for at most seven days, and access is logged against the identity that created it.

If your app already uses managed identity, user delegation SAS is the natural choice. You can then turn off shared key access on the storage account entirely.

Creating one

var service = new BlobServiceClient(
    new Uri("https://mystorage.blob.core.windows.net"), new DefaultAzureCredential());

var now = DateTimeOffset.UtcNow;
UserDelegationKey key = await service.GetUserDelegationKeyAsync(
    startsOn: now.AddMinutes(-5), expiresOn: now.AddHours(1));

var sas = new BlobSasBuilder
{
    BlobContainerName = "invoices",
    BlobName = "2026/05/INV-1042.pdf",
    Resource = "b",                          // a single blob
    StartsOn = now.AddMinutes(-5),           // allow for clock differences
    ExpiresOn = now.AddMinutes(15),
    Protocol = SasProtocol.Https
};
sas.SetPermissions(BlobSasPermissions.Read);

var uri = new BlobUriBuilder(service.Uri)
{
    BlobContainerName = sas.BlobContainerName,
    BlobName = sas.BlobName,
    Sas = sas.ToSasQueryParameters(key, service.AccountName)
}.ToUri();

The app's identity needs permission to request delegation keys. The Storage Blob Data Contributor role includes it; the narrower Storage Blob Delegator role grants just that permission.

You can cache the user delegation key and reuse it for many SAS tokens until it expires, rather than requesting a new one for every download.

Keep tokens tight

  • Short expiry. Minutes for a download link, not days. The client can always ask for a new one.
  • One blob, minimum permissions. Read on a single blob, not Read and List on the container.
  • HTTPS only.
  • Start slightly in the past, a few minutes, so small clock differences don't make a fresh token look not yet valid.

Takeaway

When you hand out blob access, use a user delegation SAS signed with your app's managed identity instead of the account key. Scope it to one blob, give it read-only access and a short lifetime, and turn off shared key access on the account once nothing depends on it.