Shared key authorization

Shared key authorization is based on two storage account access keys. The keys are string values used to build the storage account connection string URI. Format of URI is the following:

DefaultEndpointsProtocol=[http|https];AccountName=my-account-name;AccountKey=my-account-key

To view the storage account keys and connection strings navigate to Azure Portal -> Storage accounts -> select storage account -> Settings(left menu) -> Access keys. Alternatively, use the following Azure CLI command to obtain a connection string:

az storage account show-connection-string -g "resource-group-name" -n "storage-account-name"
The figure bellow shows the result of az storage account show-connection-string command:
blob-storage-connection-string

Storage account keys must be kept secret. Any of them gives full access to the storage account along with all of its services(blob, queue, table, file). By regenerating a key: any client applications using it should begin to use the newly created key in order to maintain access to the storage account. Key regeneration revokes the old key. Shared access key authorization can be entirely disabled from the configuration(Azure Portal -> Storage accounts -> select storage account -> Settings(left menu) -> Configuration -> Allow shared key access).

Azure blob is manipulated using an instance of BlobClient class. To obtain it follow the next steps:

  1. Create an instance of BlobServiceClient using the connection string
    var blobSrvClient= new BlobServiceClient(connectionString);
  2. Get access to blob container by creating a BlobContainerClient object
    BlobContainerClient container = 
           blobSrvClient.GetBlobContainerClient("container-name");
    container.CreateIfNotExists(PublicAccessType.BlobContainer);
  3. Create BlobClient object
    BlobClient blobClient = container.GetBlobClient("blob-name");
  4. Write content(from MemoryStream) to blob
  5. var response = blobClient.Upload(memStream).GetRawResponse();

Below is a C# 9 console application that connects to a storage account and performs blob CRUD operations. To run it correctly replace connection string and run the install-package Azure.Storage.Blobs command.

using System;
using System.IO;
using System.Net;
using System.Text;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using static System.Console;

namespace BlobStorageAccessKey
{
    public class BlobParameters
    {
        public string AccountName { get; init; }
        public string AccountKey { get; init; }
        public string ContainerName { get; init; }
        public static string Protocol { get { return "DefaultEndpointsProtocol=https;"; } }
    }

    class Program
    {
        readonly static string nl = Environment.NewLine;
        readonly static Encoding ASCII = Encoding.ASCII;

        static BlobContainerClient GetContainer(
            BlobParameters @params)
        {
            var name = $"AccountName={@params.AccountName};";
            var key = $"AccountKey={@params.AccountKey}";

            // build a string like:
            // DefaultEndpointsProtocol=https;AccountName=storagexj3f9;AccountKey=gGvz***rzgSZ***ZUGNO***Ydl***MRb4A==
            string connectionString = $"{BlobParameters.Protocol}{name}{key}";

            var blobServiceClient =
                new BlobServiceClient(connectionString);
            BlobContainerClient container =
                blobServiceClient.GetBlobContainerClient(@params.ContainerName);
            container.CreateIfNotExists(PublicAccessType.BlobContainer);

            return container;
        }

        static void CreateBlob(BlobClient blobClient, string text)
        {
            var content = ASCII.GetBytes(text);
            using var memStream = new MemoryStream();
            memStream.Write(content, 0, content.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            var response = blobClient.Upload(memStream).GetRawResponse();
            var created = response.Status == (int)HttpStatusCode.Created;
            if (created)
                WriteLine($"Blob \"{blobClient.Name}\" was created");
            else
                throw new Exception($"Error creating blob.");
        }

        static void UpdateBlob(BlobClient blobClient, string appendText)
        {
            var oldText = ReadBlob(blobClient);
            var newText = oldText + appendText;
            var content = ASCII.GetBytes(newText);
            // create a Stream with new content
            using var memStream = new MemoryStream();
            memStream.Write(content, 0, content.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            // upload new content to blob
            var response = blobClient.Upload(memStream, true).GetRawResponse();
            var updated = response.Status == (int)HttpStatusCode.Created;

            if (updated)
            {
                WriteLine($"Blob was updated");
                WriteLine($"New blob content:{nl}{ReadBlob(blobClient)}");
            }
            else
                throw new Exception("Error updating blob");
        }

        static string ReadBlob(BlobClient blobClient)
        {
            // read blob content
            using var memStream = new MemoryStream();
            blobClient.DownloadTo(memStream);
            var content = ASCII.GetString(memStream.ToArray());
            return content;
        }

        static void DeleteBlob(BlobContainerClient containerClient, BlobClient blobClient)
        {
            var deleted = containerClient.DeleteBlobIfExists(blobClient.Name).Value;
            if (deleted)
                WriteLine("Blob was deleted");
            else
                throw new Exception("Error deleting blob");
        }

        static void Main(string[] args)
        {
            var @params = new BlobParameters
            {
                AccountName = "storagexj3f9",
                AccountKey = "gGvz***rzgSZ***ZUGNO***Ydl***MRb4A==",
                ContainerName = "my-container"
            };

            // compose text for blob content
            var text = $"Hello blob{nl}" + 
                $"Current time: {DateTime.Now:HH:mm:ss.fff}";

            var containerClient = GetContainer(@params);
            var blobName = $"my-blob-{Guid.NewGuid():D}.txt";
            BlobClient blobClient = containerClient.GetBlobClient(blobName);
                                
            CreateBlob(blobClient, text);
            
            // read blob content
            var content = ReadBlob(blobClient);
            WriteLine($"{nl}Blob content:{nl}{content}{nl}");

            // update blob
            var updatedOn = $"{nl}" +
                $"  Updated on: {DateTime.Now:HH:mm:ss.fff}";
            UpdateBlob(blobClient, updatedOn);

            WriteLine("Press 'D' key to delete blob or press any other key to exit");
            if (ReadKey(true).Key == ConsoleKey.D)
            {
                DeleteBlob(containerClient, blobClient);
                
                WriteLine("Press any key to exit");
                ReadKey(false);
            }
        }
    }
}

Output:

Blob "my-blob-0fa1a7a3-2b30-4dcc-8634-6576a35c634d.txt" was created

Blob content:
Hello blob
Current time: 02:22:16.581

Blob was updated
New blob content:
Hello blob
Current time: 02:22:16.581
  Updated on: 02:22:17.802
Press 'D' key to delete blob or press any other key to exit

Service shared access signature

Service shared access signature(Service SAS) provides shared and narrowed access to one specific storage service(blob, queue, etc). SAS URI is built using a list of access parameters that are signed with either a storage account key or an user delegation key. In the next table are presented some of the access parameters used to create a SAS URL.

# SAS access parameter Query string parameter Notes
1 signature sig ex: sig=K8Pg9%2B***W5eC***3D
2 resource type sr container(c), blob(b), directory, snapshot
ex: sr=c
3 start and expiry date/time st & se
ex:
st=2021-08-15T21:35:11Z
se=2021-08-24T05:35:11Z
4 allowed permissions sp read, create, delete, write, add, list, etc
ex: sp=racwdl
5 client IP address sip
ex: sip=168.65.89.128-168.65.89.255
6 signed identifier si stored access policy name
7 start and expiry date/time st & se
st=2021-08-15T21:35:11Z
se=2021-08-24T05:35:11Z

SAS URL can look like one below:

https://storagexj3f9.blob.core.windows.net/my-container?sp=r&st=2021-08-15T21:35:11Z&se=2021-08-24T05:35:11Z&spr=https&sv=2020-02-10&sr=c&sig=KsPg9%2BQQ13Vyz%2GEqKcQ3C0YQF%2B3LqLoVoeAW5eCz1wA%3D

An ad hoc SAS for a container can be created following the steps: Azure Portal -> Storage accounts -> select storage account -> Blob service(left menu) -> Containers-> right-click on container -> Generate SAS(context menu). The next figure depicts how to open Generate SAS window.

create-ad-hoc-container-service-sas
Select access parameters and click Generate SAS token and URL button like in the next figure:
create-service-sas

SAS cannot be revoked unless it not expires or access keys used for signing it are regenerated. Another option is to create a SAS bound to a stored access policy. A stored access policy consists of the start time, expiry time, and permissions. You can change any of the policy parameters or delete the policy itself, such as dependent SAS will become invalid. To create an access policy for a container follow the steps: Azure Portal -> Storage accounts -> select storage account -> Containers -> select container -> Access policy(left menu) -> + Add policy button.

add-stored-access-policy-path
Select policy parameters, press OK then Save button.
create-stored-access-policy
Once access policy is created use it in Storage Explorer to create a SAS:

generate-sas-binded-access-policy
Copy generated Blob SAS token to use it later to connect only to previously selected container. This time BlobServiceClient is created using an instance of AzureSasCredential class which in turn is created using a SAS token.
var connectionString = "https://storagexj3f9.blob.core.windows.net/";
var uri = new Uri(connectionString);

var azureSasCredential = 
       new Azure.AzureSasCredential("?si=GroupC&sv=2020-02-10&sr=c&sig=G1sZHc*****0b3D");
var blobServiceClient = new BlobServiceClient(uri, azureSasCredential);

BlobContainerClient container =
       blobServiceClient.GetBlobContainerClient("my-container");