Creating a Shared Access Signature for a container or blob
The Windows Azure Blob Service supports fully authenticated requests, anonymous requests, and requests authenticated by a temporary access key referred to as a Shared Access Signature. The latter allows access to containers or blobs to be limited to only those in possession of the Shared Access Signature.
A Shared Access Signature is constructed from a combination of:
Resource (container or blob)
Access rights (read, write, delete, list)
Start time
Expiration time
These are combined into a string from which a 256-bit, hash-based message authentication code (HMAC) is generated. An access key for the storage account is used to seed the HMAC generation. This HMAC is referred to as a shared access signature. The process of generating a Shared Access Signature requires no interaction with the Blob service. A shared access signature is valid for up to one hour, which limits the allowable values for the start time and expiration time.
When using a Shared Access Signature to authenticate a request, it is submitted as one of the query string parameters. The other query parameters comprise the information from which the shared access signature was created. This allows the Blob service to create a Shared Access Signature, using the access key for the storage account, and compare it with the Shared Access Signature submitted with the request. A request is denied if it has an invalid Shared Access Signature.
An example of a storage request for a blob named theBlob
in a container named chapter1
is:
GET /chapter1/theBlob
An example of the query string parameters is:
st=2011-03-22T05%3A49%3A09Z &se=2011-03-22T06%3A39%3A09Z &sr=b &sp=r &sig=GLqbiDwYweXW4y2NczDxmWDzrJCc89oFfgBMTieGPww%3D
The st
parameter is the start time for the validity of the Shared Access Signature. The se
parameter is the expiration time for the validity of the Shared Access Signature. The sr
parameter specifies that the Shared Access Signature is for a blob. The sp
parameter specifies that the Shared Access Signature authenticates for read access only. The sig
parameter is the Shared Access Signature. A complete description of these parameters is available on MSDN at the following URL:
http://msdn.microsoft.com/en-us/library/ee395415.aspx
Once a Shared Access Signature has been created and transferred to a client, no further verification of the client is required. It is important, therefore, that the Shared Access Signature be created with the minimum period of validity and that its distribution be restricted as much as possible. It is not possible to revoke a Shared Access Signature created in this manner.
In this recipe, we will learn how to create and use a Shared Access Signature.
Getting ready
This recipe assumes the following is in the application configuration file:
<appSettings> <add key="DataConnectionString"value="DefaultEndpointsProtocol=https;AccountName={ACCOUNT_NAME};AccountKey={ACCOUNT_KEY}"/> <add key="AccountName" value="{ACCOUNT_NAME}"/> <add key="AccountKey" value="{ACCOUNT_KEY}"/> </appSettings>
We must replace {ACCOUNT_NAME}
and {ACCOUNT_KEY}
with appropriate values for the account name and access key.
How to do it...
We are going to create and use Shared Access Signatures for a blob. We do this as follows:
Add a class named
SharedAccessSignaturesExample
to the project.Add the following
using
statements to the top of the class file:using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.StorageClient; using System.Configuration; using System.Net; using System.IO; using System.Security.Cryptography;
Add the following private members to the class:
private String blobEndpoint; private String accountName; private String accountKey;
Add the following constructor to the class:
SharedAccessSignaturesExample() { CloudStorageAccount cloudStorageAccount =CloudStorageAccount.Parse(ConfigurationManager.AppSettings["DataConnectionString"]); blobEndpoint =cloudStorageAccount.BlobEndpoint.AbsoluteUri; accountName = cloudStorageAccount.Credentials.AccountName; StorageCredentialsAccountAndKey accountAndKey =cloudStorageAccount.Credentials asStorageCredentialsAccountAndKey; accountKey =accountAndKey.Credentials.ExportBase64EncodedKey(); }
Add the following method, creating a container and blob, to the class:
private void CreateContainerAndBlob(String containerName,String blobName) { CloudStorageAccount cloudStorageAccount =CloudStorageAccount.Parse(ConfigurationManager.AppSettings["DataConnectionString"]); CloudBlobClient cloudBlobClient =cloudStorageAccount.CreateCloudBlobClient(); CloudBlobContainer cloudBlobContainer =new CloudBlobContainer(containerName, cloudBlobClient); cloudBlobContainer.Create(); CloudBlockBlob cloudBlockBlob =cloudBlobContainer.GetBlockBlobReference(blobName); cloudBlockBlob.UploadText("This weak and idle theme."); }
Add the following method, getting a Shared Access Signature, to the class:
private String GetSharedAccessSignature(String containerName, String blobName) { SharedAccessPolicy sharedAccessPolicy =new SharedAccessPolicy() { Permissions = SharedAccessPermissions.Read, SharedAccessStartTime =DateTime.UtcNow.AddMinutes(-10d), SharedAccessExpiryTime =DateTime.UtcNow.AddMinutes(40d) }; CloudStorageAccount cloudStorageAccount =CloudStorageAccount.Parse(ConfigurationManager.AppSettings["DataConnectionString"]); CloudBlobClient cloudBlobClient =cloudStorageAccount.CreateCloudBlobClient(); CloudBlobContainer cloudBlobContainer =new CloudBlobContainer(containerName, cloudBlobClient); CloudBlockBlob cloudBlockBlob =cloudBlobContainer.GetBlockBlobReference(blobName); String sharedAccessSignature =cloudBlockBlob.GetSharedAccessSignature(sharedAccessPolicy); return sharedAccessSignature; }
Add the following method, creating a Shared Access Signature, to the class:
private String CreateSharedAccessSignature(String containerName, String blobName, String permissions) { String iso8061Format = "{0:yyyy-MM-ddTHH:mm:ssZ}"; DateTime startTime = DateTime.UtcNow; DateTime expiryTime = startTime.AddHours(1d); String start = String.Format(iso8061Format, startTime); String expiry = String.Format(iso8061Format, expiryTime); String stringToSign =String.Format("{0}\n{1}\n{2}\n/{3}/{4}\n",permissions, start, expiry, accountName, containerName); String rawSignature = String.Empty; Byte[] keyBytes = Convert.FromBase64String(accountKey); using (HMACSHA256 hmacSha256 = new HMACSHA256(keyBytes)) { Byte[] utf8EncodedStringToSign =System.Text.Encoding.UTF8.GetBytes(stringToSign); Byte[] signatureBytes =hmacSha256.ComputeHash(utf8EncodedStringToSign); rawSignature = Convert.ToBase64String(signatureBytes); } String sharedAccessSignature =String.Format("?st={0}&se={1}&sr=c&sp={2}&sig={3}",Uri.EscapeDataString(start),Uri.EscapeDataString(expiry),permissions,Uri.EscapeDataString(rawSignature)); return sharedAccessSignature; }
Add the following method, authenticating with a Shared Access Signature, to the class:
private void AuthenticateWithSharedAccessSignature(String containerName, String blobName,String sharedAccessSignature) { StorageCredentialsSharedAccessSignature storageCredentials= new StorageCredentialsSharedAccessSignature(sharedAccessSignature); CloudBlobClient cloudBlobClient =new CloudBlobClient(blobEndpoint, storageCredentials); CloudBlobContainer cloudBlobContainer =new CloudBlobContainer(containerName, cloudBlobClient); CloudBlockBlob cloudBlockBlob =cloudBlobContainer.GetBlockBlobReference(blobName); String blobText = cloudBlockBlob.DownloadText(); }
Add the following method, using a Shared Access Signature, to the class:
private void UseSharedAccessSignature(String containerName, String blobName,String sharedAccessSignature) { String requestMethod = "GET"; String urlPath = String.Format("{0}{1}/{2}{3}", blobEndpoint, containerName, blobName,sharedAccessSignature); Uri uri = new Uri(urlPath); HttpWebRequest request =(HttpWebRequest)WebRequest.Create(uri); request.Method = requestMethod; using (HttpWebResponse response =(HttpWebResponse)request.GetResponse()) { Stream dataStream = response.GetResponseStream(); using (StreamReader reader =new StreamReader(dataStream)) { String responseFromServer = reader.ReadToEnd(); } } }
Add the following method, using the methods added earlier, to the class:
public static void UseSharedAccessSignaturesExample() { String containerName = "{CONTAINER_NAME}"; String blobName = "{BLOB_NAME}"; SharedAccessSignaturesExample example =new SharedAccessSignaturesExample(); example.CreateContainerAndBlob(containerName, blobName); String sharedAccessSignature1 =example.GetSharedAccessSignature(containerName, blobName); example.AuthenticateWithSharedAccessSignature(containerName, blobName, sharedAccessSignature1); String sharedAccessSignature2 =example.CreateSharedAccessSignature(containerName, blobName, "rw"); example.UseSharedAccessSignature(containerName, blobName, sharedAccessSignature2); }
How it works...
In steps 1 and 2, we set up the class. In step 3, we add some private members for the blob endpoint, as well as the account name and access key which we initialize in the constructor we add in step 4. In step 5, we create a container and upload a blob to it.
In step 6, we use the GetSharedAccessSignature()
method of the CloudBlockBlob
class to get a shared access signature based on a SharedAccessPolicy
we pass into it. In this SharedAccessPolicy
, we specify that we want read access on a blob from 10 minutes earlier to 40 minutes later than the current time. The fuzzing of the start time is to minimize any risk of the time on the local machine being too far out of sync with the time on the storage service. This approach is the easiest way to get a shared access signature.
In step 7, we construct a Shared Access Signature from first principles. This version does not use the Storage Client library. We generate a string to sign from the account name, desired permissions, start, and expiration time. We initialize an HMACSHA256
instance from the access key, and use this to generate an HMAC from the string to sign. We then create the remainder of the query string while ensuring that the data is correctly URL encoded.
In step 8, we use a shared access signature to initialize a StorageCredentialsSharedAccessSignature
instance, which we use to create a CloudBlobClient
instance. We use this to construct the CloudBlobContainer
and CloudBlobClient
instances we use to download the content of a blob.
In step 9, we use HttpWebRequest
and HttpWebResponse
objects to perform an anonymous download of the content of a blob. We construct the query string for the request using the Shared Access Signature and direct the request to the appropriate blob endpoint. Note that when constructing urlPath
for the storage emulator, we must add a /
between {0}
and {1}
because of a difference in the way the Storage Client library constructs the endpoint for the storage emulator and the storage service. For example, we need to use the following for the storage emulator:
String urlPath = String.Format("{0}/{1}/{2}{3}",blobEndpoint, containerName, blobName,sharedAccessSignature);
In step 10, we add a helper method that invokes all the methods we added earlier. We must replace {CONTAINER_NAME}
and {BLOB_NAME}
with appropriate names for the container and blob.
There's more...
In step 7, we could create a Shared Access Signature based on a container-level access policy by replacing the definition of stringToSign
with the following:
String stringToSign = String.Format("\n\n\n/{0}/{1}\n{2}", accountName, containerName, policyId);
policyId
specifies the name of a container-level access policy.
See also
The recipe named Using a container-level access policy in this chapter