Over a million developers have joined DZone.

Windows Azure Storage magic with Shared Access Signatures

· Cloud Zone

Download this eBook outlining the critical components of success for SaaS companies - and the new rules you need to play by.  Brought to you in partnership with NuoDB.

When building cloud applications on Windows Azure, it’s always a good thing to delegate as much work to specialized services as possible. File downloads would be one good example: these can be streamed directly from Windows Azure blob storage to your client, without having to pass a web application hosted on Windows Azure Cloud Services or Web Sites. Why occupy the web server with copying data from a request stream to a response stream? Let blob storage handle it!

When thinking this through there may be some issues you may think of. Here are a few:

  • How can I keep this blob secure? I don’t want to give everyone access to it!
  • How can I keep the download URL on my web server, track the number of downloads (or enforce other security rules) and still benefit from offloading the download to blob storage?
  • How can the blob be stored in a way that is clear to my application (e.g. a customer ID or something), yet give it a friendly name when downloading?

Let’s answer these!

Meet Shared Access Signatures

Keeping blobs secure is pretty easy on Windows Azure Blob Storage, but it’s also sort of an all-or-nothing story… Either you make all blobs in a container private, or you make them public.

Not to worry though! Using Shared Access Signatures it is possible to grant temporary privileges on a blob, for read and write access. Here’s a code snippet that will grant read access to the blob named helloworld.txt, residing in a private container named files, during the next minute:

CloudStorageAccount account = // your storage account connection here
var client = account.CreateCloudBlobClient();
var container = client.GetContainerReference("files");
var blob = container.GetBlockBlobReference("helloworld.txt");

var builder = new UriBuilder(blob.Uri);
builder.Query = blob.GetSharedAccessSignature(
    new SharedAccessBlobPolicy
    {
        Permissions = SharedAccessBlobPermissions.Read,
        SharedAccessStartTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(-5)),
        SharedAccessExpiryTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(1)
    }).TrimStart('?');

var signedBlobUrl = builder.Uri;

Note I’m giving access starting 5 minutes ago, just to make sure any clock skew along the way is ignored within a reasonable time window.

There we go: our blob is secured and by passing along the signedBlobUrl to our user, he or she can start downloading our blob without having access to any other blobs at all.

Meet HTTP redirects

Shared Access Signatures are really cool, but the generated URLs are… “fugly”, they are not pretty or easy to remember. Well, there is this thing called HTTP redirects, right? Here’s an ASP.NET MVC action method that checks if the user is authenticated, queries a repository for the correct filename, generates the signed access signature and redirects us to the actual download.

[Authorize]
[EnsureInvoiceAccessibleForUser]
public ActionResult DownloadInvoice(string invoiceId)
{
    // Fetch invoice
    var invoice = InvoiceService.RetrieveInvoice(invoiceId);
    if (invoice == null)
    {
        return new HttpNotFoundResult();
    }
    
    // We can do other things here: track # downloads, ...

    // Build shared access signature
    CloudStorageAccount account = // your storage account connection here
    var client = account.CreateCloudBlobClient();
    var container = client.GetContainerReference("invoices");
    var blob = container.GetBlockBlobReference(invoice.CustomerId + "-" + invoice.InvoiceId);

    var builder = new UriBuilder(blob.Uri);
    builder.Query = blob.GetSharedAccessSignature(
        new SharedAccessBlobPolicy
        {
            Permissions = SharedAccessBlobPermissions.Read,
            SharedAccessStartTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(-5)),
            SharedAccessExpiryTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(1)
        }).TrimStart('?');

    var signedBlobUrl = builder.Uri;

    // Redirect
    return Redirect(signedBlobUrl);
}

This gives us the best of both worlds: our web application can still verify access and run some business logic on it, yet we can offload the file download to blob storage.

Meet Shared Access Signatures content disposition header

Often, storage is a technical thing where we choose technical filenames for the things we store, instead of human-readable or human-friendly file names. In the example above, users will get a very strange filename to be downloaded: the customer id + invoice id, concatenated. No .pdf file extension, nothing else either. Users may get confused by this, or have problems opening the file because teir browser will not recognize this is a PDF.

Last November, a feature was added to blob storage which enables us to let a blob be whatever we want it to be: support for setting some additional headers on a blob through the Shared Access Signature.

The following headers can be specified on-the-fly, through the shared access signature:

  • Cache-Control
  • Content-Disposition
  • Content-Encoding
  • Content-Language
  • Content-Type

Here’s how to generate a meaningful Shared Access Signature in the previous example, where we specify a human-readable filename for the resulting download, as well as a custom content type:

builder.Query = blob.GetSharedAccessSignature(
    new SharedAccessBlobPolicy
    {
        Permissions = SharedAccessBlobPermissions.Read,
        SharedAccessStartTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(-5)),
        SharedAccessExpiryTime = new DateTimeOffset(DateTime.UtcNow.AddMinutes(1)
    },
    new SharedAccessBlobHeaders
    {
        ContentDisposition = "attachment; filename="
            + customer.DisplayName + "-invoice-" + invoice.InvoiceId + ".pdf",
        ContentType = "application/pdf"
    }).TrimStart('?');

Note: for this feature to work, the service version for the storage account must be set to the latest one, using the DefaultServiceVersion on the blob client. Here’s an example:

CloudStorageAccount account = // your storage account connection here
var client = account.CreateCloudBlobClient();
var serviceProperties = client.GetServiceProperties();
serviceProperties.DefaultServiceVersion = "2013-08-15";
client.SetServiceProperties(serviceProperties);

Combining all these techniques, we can do some analytics and business logic in our web application and offload the boring file and network I/O to blob storage.

Enjoy!

Learn how moving from a traditional, on-premises delivery model to a cloud-based, software-as-a-service (SaaS) strategy is a high-stakes, bet-the-company game for independent software vendors. Brought to you in partnership with NuoDB.

Topics:

Published at DZone with permission of Maarten Balliauw, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}