35

I used to be able to create a shared access signature on a Blob using the v11 Azure SDK API, like this:

var containerName = "mycontainer";
var blobName = "myblob";

CloudStorageAccount storageAccount 
 = CloudStorageAccount.Parse(<StorageConnectionString>);

CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

CloudBlobContainer container = blobClient.GetContainerReference(containerName);


SharedAccessBlobPermissions permission = SharedAccessBlobPermissions.Read;

TimeSpan clockSkew = TimeSpan.FromMinutes(15d);
TimeSpan accessDuration = TimeSpan.FromMinutes(15d);

var blobSAS = new SharedAccessBlobPolicy
{
    SharedAccessStartTime = DateTime.UtcNow.Subtract(clockSkew),
    SharedAccessExpiryTime = DateTime.UtcNow.Add(accessDuration) + clockSkew,
    Permissions = permissions
};

CloudBlockBlob blob = container.GetBlockBlobReference(blobName);

string sasBlobToken = blob.GetSharedAccessSignature(blobSAS);

...

I want to use the latest v12 .NET API which seems to replace CloudBlobClient by BlobServiceClient, CloudBlobContainer by BlobContainerClient and CloudBlockBlob by BlobClient.

However the method GetSharedAccessSignature that is available on a CloudBlockBlob instance is not available on a BlobClient instance.

Question

How to get a shared access signature from a BlobClient instance using the latest Azure SDK .NET API v12?

Kzryzstof
  • 7,688
  • 10
  • 61
  • 108

7 Answers7

48

Sajeetharan's answer made me look for a BlobSasBuilder class, which actually exists.

Here is how I can build one on the server:

//  Creates a client to the BlobService using the connection string.
var blobServiceClient = new BlobServiceClient(storageConnectionString);

//  Gets a reference to the container.
var blobContainerClient = blobServiceClient.GetBlobContainerClient(<ContainerName>);

//  Gets a reference to the blob in the container
BlobClient blobClient = containerClient.GetBlobClient(<BlobName>);

//  Defines the resource being accessed and for how long the access is allowed.
var blobSasBuilder = new BlobSasBuilder
{
    StartsOn = DateTime.UtcNow.Subtract(clockSkew), 
    ExpiresOn = DateTime.UtcNow.Add(accessDuration) + clockSkew,
    BlobContainerName = <ContainerName>,
    BlobName = <BlobName>,
};
    
//  Defines the type of permission.
blobSasBuilder.SetPermissions(BlobSasPermissions.Write);
       
//  Builds an instance of StorageSharedKeyCredential      
var storageSharedKeyCredential = new StorageSharedKeyCredential(<AccountName>, <AccountKey>);

//  Builds the Sas URI.
BlobSasQueryParameters sasQueryParameters = blobSasBuilder.ToSasQueryParameters(storageSharedKeyCredential);

Here is how to use it on the client side:

//  Builds the URI to the blob storage.
UriBuilder fullUri = new UriBuilder()
{
    Scheme = "https",
    Host = string.Format("{0}.blob.core.windows.net", <AccountName>),
    Path = string.Format("{0}/{1}", <ContainerName>, <BlobName>),
    Query = sasQueryParameters.ToString()
};

//  Get an instance of BlobClient using the URI.
var blobClient = new BlobClient(fullUri.Uri, null);

//  Upload stuff in the blob.
await blobClient.UploadAsync(stream);

Addendum

As mentioned by @one2012 in the comments, a page has been put up few months later after this answer showcasing all the features found in the Azure.Storage namespaces. The link can be useful to get more information.

Update

On the server-side, I have an Azure Function that is now connecting a Azure Storage with the Function's Managed Identity. When I connect the storage, I am not using an account anymore, only the endpoint of the storage:

BlobContainerClient blobContainerClient = new(new Uri(containerEndpoint), new DefaultAzureCredential());  

This makes the following part from the initial server code a bit trickier because I used to use the CloudStorageAccount.Credentials.GetExportKeys() method to get the account's key. When using the Managed Identity, it seems I do not have access to it anymore:

//  Builds an instance of StorageSharedKeyCredential      
    var storageSharedKeyCredential = new StorageSharedKeyCredential(<AccountName>, <AccountKey>);

It turns out I have to use User Delegation to build a SAS Uri:

...
BlobServiceClient blobServiceClient = blobClient.GetParentBlobContainerClient().GetParentBlobServiceClient();

UserDelegationKey userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync
(
    DateTimeOffset.UtcNow,
    DateTimeOffset.UtcNow.AddMinutes(5d)
);
            
BlobUriBuilder blobUriBuilder = new (blobClient.Uri)
{
    // Specify the user delegation key.
    Sas = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName)
};

string uri = blobUriBuilder.ToUri();

    
Kzryzstof
  • 7,688
  • 10
  • 61
  • 108
  • 7
    I can't find a way to get the storageSharedKeyCredential from the Connection String instead of using the AccountName & AccountKey. Do you know any way? – Benjamin Talmard Feb 08 '20 at 22:02
  • 10
    @benjamin `var csBuilder = new DbConnectionStringBuilder(); csBuilder.ConnectionString = _configuration.GetConnectionString("MyCS"); var storageSharedKeyCredential = new StorageSharedKeyCredential( (string)csBuilder["AccountName"], (string)csBuilder["AccountKey"]);` – Nethemas Apr 01 '20 at 00:32
  • 5
    if you are moving to Azure.Storage namespaces this page is the most helpful thing I've found https://www.craftedforeveryone.com/beginners-guide-and-reference-to-azure-blob-storage-sdk-v12-dot-net-csharp – ono2012 Aug 12 '20 at 12:44
  • 1
    you, Sir, saved my life and my mental health – Bogdan Banciu Nov 22 '20 at 19:16
  • Took me a while to figure out my SDK was out of date. This version or later supports all the features in this answer: – IdusOrtus May 27 '21 at 18:17
  • is the similar library available for Eventhub? – Kiran Ramchandra Parab Nov 03 '21 at 15:36
9

After a fair amount of hunting, I have located some Microsoft documentation on this: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet

This details using a user delegation key to generate the SAS instead of the account key but the change is just a different overload to .ToSasQueryParameters() as described in other answers.

Some key snippets from the article to hook this up. First create your BlobServiceClient:

// Construct the blob endpoint from the account name.
string blobEndpoint = string.Format("https://{0}.blob.core.windows.net", accountName);

// Create a new Blob service client with Azure AD credentials.
BlobServiceClient blobClient = new BlobServiceClient(new Uri(blobEndpoint),
                                                     new DefaultAzureCredential());

Get the user delegation key, this will be used to generate the SAS:

// Get a user delegation key for the Blob service that's valid for seven days.
// You can use the key to generate any number of shared access signatures over the lifetime of the key.
UserDelegationKey key = await blobClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow,
                                                                   DateTimeOffset.UtcNow.AddDays(7));

Finally create the SAS URI:

// Create a SAS token that's valid for one hour.
BlobSasBuilder sasBuilder = new BlobSasBuilder()
{
    BlobContainerName = containerName,
    BlobName = blobName,
    Resource = "b",
    StartsOn = DateTimeOffset.UtcNow,
    ExpiresOn = DateTimeOffset.UtcNow.AddHours(1)
};

// Specify read permissions for the SAS.
sasBuilder.SetPermissions(BlobSasPermissions.Read);

// Use the key to get the SAS token.
string sasToken = sasBuilder.ToSasQueryParameters(key, accountName).ToString();

// Construct the full URI, including the SAS token.
UriBuilder fullUri = new UriBuilder()
{
    Scheme = "https",
    Host = string.Format("{0}.blob.core.windows.net", accountName),
    Path = string.Format("{0}/{1}", containerName, blobName),
    Query = sasToken
};
Chris Alexander
  • 1,212
  • 14
  • 19
5

Using Azure Blob storage client library v12 for .NET:

BlobSasBuilder blobSasBuilder = new BlobSasBuilder()
{
    BlobContainerName = blobContainerName,
    BlobName = blobName,
    Resource = "b", //b = blob, c = container
    StartsOn = DateTimeOffset.UtcNow,
    ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(lifetimeMinutes)
};

blobSasBuilder.SetPermissions(BlobSasPermissions.Read);

StorageSharedKeyCredential storageSharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);

string sas = blobSasBuilder.ToSasQueryParameters(storageSharedKeyCredential).ToString();

In case if the Shared Access Signature (SAS Token) has to generated based on the access policy assigned to a container then use the below method

BlobSasBuilder blobSasBuilder = new BlobSasBuilder()
{
    BlobContainerName = blobContainerName,
    BlobName = blobName,
    Resource = "b", //b = blob, c = container
    Identifier = "ReadOnlyPolicy" //string value referees to the access policy created and assigned to the container.
};

StorageSharedKeyCredential storageSharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);

string sas = blobSasBuilder.ToSasQueryParameters(storageSharedKeyCredential).ToString();

Note: When SAS token generation is based on the access policy assigned to the container you will not be able to define the permissions, start or end time to in BlobSasBuilder. You will be getting runtime exception as "Access policy fields can be associated with signature or SAS identifier but not both"

Reference: https://www.craftedforeveryone.com/beginners-guide-and-reference-to-azure-blob-storage-sdk-v12-dot-net-csharp/

https://www.craftedforeveryone.com/beginners-guide-and-reference-to-azure-blob-storage-sdk-v12-dot-net-csharp/#generate_access_policy_based_sas_token_for_a_blob

Kaarthikeyan
  • 500
  • 5
  • 12
1

On here: https://learn.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature is states that Azure Storage supports three different types of shared access signature (SAS):

  1. Account level SAS, which is what you were using in the v11 SDK. Details & example here: https://learn.microsoft.com/en-us/azure/storage/common/storage-account-sas-create-dotnet
  2. Service level SAS, using v12 (& V11) SDK. Details & example here: https://learn.microsoft.com/en-us/azure/storage/blobs/sas-service-create?tabs=dotnet
  3. User delegation SAS, which is Microsoft's recommended approach, providing you can use a Azure Active Directory user to sign against using the v12 SDK. Details & example here: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet

1 & 2 both use the account's shared key to generate the SAS token, whilst 3 uses a key generated from the AAD account user, so is more secure and more easily targetable for revoking if needed (in theory). See https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas#:~:text=Authorization%20of%20a%20user%20delegation%20SAS,-When%20a%20client&text=This%20approach%20provides%20an%20additional,is%20a%20security%20best%20practice. for more details on why this is more secure ("This approach provides an additional level of security and avoids the need to store your account access key with your application code. For these reasons, creating a SAS using Azure AD credentials is a security best practice.")

Now all are supported by the storage account to use, but I get the impression that the account level is either not supported to implement in v12 SDK (I'm surmising - so don't quote me, but I can't find this either) or there is some other hidden way to do this (certainly the BlobSasBuilder.ToSasQueryParameters() method has only the two overloads), which I guess leaves the user delegation or service level approaches as what you are now meant to implement.

Darren
  • 11
  • 2
0

Using Azure Blob storage client library v12 for .NET:

        BlobSasBuilder blobSasBuilder = new BlobSasBuilder()
        {
            BlobContainerName = blobContainerName,
            BlobName = blobName,
            Resource = "b", //b = blob, c = container
            StartsOn = DateTimeOffset.UtcNow,
            ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(lifetimeMinutes)
        };

        blobSasBuilder.SetPermissions(BlobSasPermissions.Read);

        StorageSharedKeyCredential storageSharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);

        string sas = blobSasBuilder.ToSasQueryParameters(storageSharedKeyCredential).ToString();
below43
  • 509
  • 6
  • 8
0
private string BuildSASUri(BlobClient blob)
{
    // Create a user SAS that only allows reading for a minute
    BlobSasBuilder sas = new BlobSasBuilder 
    {
        BlobContainerName = blob.BlobContainerName,
        BlobName = blob.Name,
        Resource = "b",
        ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(1)
    };
    // Allow read access
    sas.SetPermissions(BlobSasPermissions.Read);
    var storageSharedKeyCredential = new StorageSharedKeyCredential(
        _iconfiguration.GetValue<string>("StorageAccount:AccountName"),
        _iconfiguration.GetValue<string>("StorageAccount:AccountKey")
    );

    return sas.ToSasQueryParameters(storageSharedKeyCredential).ToString();
}

The above is my working code.

However, I can't work out how to create a stored access policy with V12. It should be this: https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs.blobcontainerclient.setaccesspolicy?view=azure-dotnet

But I think Microsoft have completely forgotten to provide a way to create BlobSignedIdentifier.

The docs are out of date: https://learn.microsoft.com/en-us/azure/storage/common/storage-stored-access-policy-define-dotnet?toc=%2fazure%2fstorage%2fblobs%2ftoc.json

Which uses Microsoft.Azure.Storage.Blob, but https://www.nuget.org/packages/Microsoft.Azure.Storage.Blob/ says don't use it anymore.

0

In my case the solution seems to be quite simple and elegant:

My Old code:

    using Microsoft.Azure.Storage.Blob;
    private readonly CloudBlobContainer _container;
    private Uri GetSharedAccessUri(string filename)
    {
        var blob = _container.GetBlockBlobReference(filename);
        var sas = blob.GetSharedAccessSignature(new SharedAccessBlobPolicy
        {
            Permissions = SharedAccessBlobPermissions.Read,
            SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddYears(1)
        });
        return new Uri(blob.Uri, sas);
    }

My New code:

    using Azure.Storage.Blobs;
    private readonly BlobContainerClient _container;
    private Uri GetSharedAccessUri(string filename)
    {
        var blob = _container.GetBlockBlobClient(filename);
        var sas = blob.GenerateSasUri((BlobSasPermissions)BlobContainerSasPermissions.Read, DateTime.UtcNow.AddYears(1));

        return new Uri(blob.Uri, sas);
    }
KVN
  • 863
  • 1
  • 17
  • 35