Windows 8 How to upload an Image using a Blob Storage SAS generated by Windows Azure Mobile Services

Updated: Please read the Background on Shared Access Signatures from this post then move to this updated post – How to upload an Image to Windows Azure Storage using Mobile Services – that will show you how to make use of the Windows Azure Storage SDK for Node.

Windows 8 How to upload an Image using Blob Storage SAS generated by Windows Azure Mobile Services

This post details the specific scenario on how to capture an image on windows 8 and upload it directly to Windows Azure Blob Storage using a Shared Access Signature (SAS) generated within Windows Azure Mobile Services. It demonstrates and alternative approach suited for larger scale implementations (i.e using a SAS) when contrasted with the following article Storing Images from Android in Windows Azure Mobile Services

Note: This topic is advanced and assumes that you have a good knowledge of the Windows Azure Blob REST API and Windows Azure Mobile Services. I would suggest you check out the following tutorials and Blob Storage REST API prior to starting
Note: that although this is specifically an image example you could upload any media/binary data to blob storage using the same approach.

Background – Shared Access Signature

What Are Shared Access Signatures?

A Shared Access Signature is a URL that grants access rights to containers, blobs, queues, and tables. By specifying a Shared Access Signature, you can grant users who have the URL access to a specific resource for a specified period of time. You can also specify what operations can be performed on a resource that’s accessed via a Shared Access Signature. In the case of Blobs operations include:

  • Reading and writing page or block blob content, block lists, properties, and metadata
  • Deleting, leasing, and creating a snapshot of a blob
  • Listing the blobs within a container

Why not just use the storage account name and key directly?

There are a few standout reasons:

  • Security – When building device applications you should not store your storage account name and key within the device app. The reason is that it makes your storage account susceptible to being misused. If someone were to reverse engineer your application take your storage account key then they would essentially have access to 100TB of cloud based storage until such a time that you realized and reset the key. The safer approach is to use a SAS as it provides a time boxed token with defined permissions to a defined resource. With policies the token can also be invalidated/revoked
  • Scale Out (and associated costs)- A common approach I see is uploading an image directly through their web tier e.g a Web API or Mobile Service unfortunate consequence of this at scale is that you are unnecessarily loading your web tier. Consider that each of your instances on your web tier has a limited network I/O. Uploading images directly through this will result in maxing out that I/O and the need to scale out (add more instances) much sooner then alternative approaches. Now consider a scenario where your application requests only a SAS from your web tier you have now moved MBs or image load off your web tier and instead replaced it with a small ~ 100 – 200 byte SAS. This essentially means a single instance now will provide much more throughput and your upload I/O now directly hits the Blob storage service

What is the general workflow for uploading a blob using a SAS?

The four basic steps required when uploading an image using the SAS approach depicted are as follows:

  1. Request a SAS from your service
  2. SAS returned from your service
  3. Upload blob (image/video/binary data) directly to Blob Storage using the SAS
  4. Storage service returns response

For this post we will focus specifically on how to write/upload a blob using a shared access signature that is generated in the mobile service insert trigger.

Creating your Mobile Service

In this post I will extend the Mobile Services quick start sample. Before proceeding to the next section create a mobile service and download the quickstart as detailed in the tutorial here

Capturing the Image|Media

Our first task is to capture the media we wish to upload. To do this follow the following steps.

  1. Add an AppBar to MainPage.xaml with a take photo button to allow us to capture the image
  2. 1 2 3 4 5 6 7 8 9 10 11
    ...
    </Grid>
    ...
    <Page.BottomAppBar>
    <AppBar>
    <Button Name="btnTakePhoto" Style="{StaticResource PhotoAppBarButtonStyle}"
    Click="OnTakePhotoClick" />
    </AppBar>
    </Page.BottomAppBar>
    ...
    </Page>
    view raw gistfile1.cs hosted with ❤ by GitHub
  3. Add the OnTakePhotoClick handler and use the CameraCaptureUI class for taking photo and video
  4. 1 2 3 4 5 6 7 8 9 10 11
    using Windows.Media.Capture;
     
     
    private async void OnTakePhotoClick(object sender, RoutedEventArgs e)
    {
    //Take photo or video
    CameraCaptureUI cameraCapture = new CameraCaptureUI();
    StorageFile media = await cameraCapture.CaptureFileAsync(CameraCaptureUIMode.PhotoOrVideo);
     
     
    }
    view raw gistfile1.cs hosted with ❤ by GitHub

Generating a Shared Access Signature (SAS) using Mobile Services server-side script

In this step we add sever-side script to generate a SAS on insert operation of the TodoItem table.

To do this perform the following steps:

  1. Navigate to your Mobile Service and select the Data Tab, then click on Todoitem
  2. Select Script, then the Insert drop down
  3. Add the following server side script to generate the SAS
  4. Note: this code assumes there is already a public container called test.
    Note:Simple example of Generating a Windows Azure blob SAS in Node created using the guidance here.

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
    //Simple example of Generating a Windows Azure blob SAS in Node created using the guidance at http://msdn.microsoft.com/en-us/library/windowsazure/hh508996.aspx.
    //If your environment has access to the Windows Azure SDK for Node (https://github.com/WindowsAzure/azure-sdk-for-node) then you should use that instead.
     
     
    function insert(item, user, request) {
    var accountName = '<Your Account Name>';
    var accountKey = '<Your Account Key>';
    //Note: this code assumes the container already exists in blob storage.
    // If you wish to dynamically create the container then implement guidance here - http://msdn.microsoft.com/en-us/library/windowsazure/dd179468.aspx
    var container = 'test';
    var imageName = item.ImageName;
    item.SAS = getBlobSharedAccessSignature(accountName, accountKey, container, imageName);
    request.execute();
    }
     
     
    function getBlobSharedAccessSignature(accountName, accountKey, container, fileName){
    signedExpiry = new Date();
    signedExpiry.setMinutes(signedExpiry.getMinutes() + 30);
    canonicalizedResource = util.format(canonicalizedResource, accountName, container, fileName);
    signature = getSignature(accountKey);
    var queryString = getQueryString();
    return util.format(resource, accountName, container, fileName, queryString);
    }
     
     
    function getSignature(accountKey){
    var decodedKey = new Buffer(accountKey, 'base64');
    var stringToSign = signedPermissions + "\n" + signedStart + "\n" + getISO8601NoMilliSeconds(signedExpiry) + "\n" + canonicalizedResource + "\n" + signedIdentifier + "\n" + signedVersion;
    stringToSign = stringToSign.toString('UTF8');
    return crypto.createHmac('sha256', decodedKey).update(stringToSign).digest('base64');
    }
     
     
    function getQueryString(){
    var queryString = "?";
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_VERSION, '2012-02-12');
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_RESOURCE, signedResource);
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_START, getISO8601NoMilliSeconds(signedStart));
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_EXPIRY, getISO8601NoMilliSeconds(signedExpiry));
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_PERMISSIONS, signedPermissions);
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNATURE, signature);
    queryString += addEscapedIfNotNull(queryString, Constants.SIGNED_IDENTIFIER, signedIdentifier);
    return queryString;
    }
     
     
    function addEscapedIfNotNull(queryString, name, val){
    var result = '';
    if(val)
    {
    var delimiter = (queryString.length > 1) ? '&' : '' ;
    result = util.format('%s%s=%s', delimiter, name, encodeURIComponent(val));
    }
    return result;
    }
     
     
    function getISO8601NoMilliSeconds(date){
    if(date)
    {
    var raw = date.toJSON();
    //blob service does not like milliseconds on the end of the time so strip
    return raw.substr(0, raw.lastIndexOf('.')) + 'Z';
    }
    }
     
     
    var Constants = {
    SIGNED_VERSION: 'sv',
    SIGNED_RESOURCE: 'sr',
    SIGNED_START: 'st',
    SIGNED_EXPIRY: 'se',
    SIGNED_PERMISSIONS: 'sp',
    SIGNED_IDENTIFIER: 'si',
    SIGNATURE: 'sig',
    };
     
     
    var crypto = require('crypto');
    var util = require('util');
     
     
    //http://msdn.microsoft.com/en-us/library/windowsazure/hh508996.aspx
    var resource = 'https://%s.blob.core.windows.net/%s/%s%s';
     
     
    //Version of the storage rest API
    var signedVersion = '2012-02-12';
     
     
    //signedResource. use b for blob, c for container
    var signedResource = 'b'; //
     
     
    // The signedpermission portion of the string must include the permission designations in a fixed order that is specific to each resource type. Any combination of these permissions is acceptable, but the order of permission letters must match the order in the following table.
    var signedPermissions = 'rw'; //blob perms must be in this order rwd
     
     
    // Example - Use ISO 8061 format
    var signedStart = '';
    var signedExpiry = '';
     
     
    // Eample Blob
    // URL = https://myaccount.blob.core.windows.net/music/intro.mp3
    // canonicalizedresource = "/myaccount/music/intro.mp3"
    var canonicalizedResource = '/%s/%s/%s';
     
     
    //The string-to-sign is a unique string constructed from the fields that must be verified in order to authenticate the request. The signature is an HMAC computed over the string-to-sign and key using the SHA256 algorithm, and then encoded using Base64 encoding.
    var signature = '';
     
     
    //Optional. A unique value up to 64 characters in length that correlates to an access policy specified for the container, queue, or table.
    var signedIdentifier = '';
    view raw gistfile1.js hosted with ❤ by GitHub

    Uploading the Image directly to storage using the SAS

  5. To generate the sas we must insert a todoItem. which will now return the SAS property for the image. Thus update the OnTakePhotoClick handler to insert an item.
  6. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    private async void OnTakePhotoClick(object sender, RoutedEventArgs e)
    {
    //Take photo or video
    CameraCaptureUI cameraCapture = new CameraCaptureUI();
    StorageFile media = await cameraCapture.CaptureFileAsync(CameraCaptureUIMode.PhotoOrVideo);
     
     
    //add todo item to trigger insert operation which returns item.SAS
    var todoItem = new TodoItem() { Text = "test image", ImageName = media.Name };
    await todoTable.InsertAsync(todoItem);
    items.Add(todoItem);
     
     
    //TODO: Upload image direct to blob storage using SAS
    }
    view raw gistfile1.cs hosted with ❤ by GitHub
  7. Update OnTakePhotoClick handler to update the image directly to blob storage using the HttpClient and the generated item.SAS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
private async void OnTakePhotoClick(object sender, RoutedEventArgs e)
{
//Take photo or video
CameraCaptureUI cameraCapture = new CameraCaptureUI();
StorageFile media = await cameraCapture.CaptureFileAsync(CameraCaptureUIMode.PhotoOrVideo);
 
 
//add todo item
var todoItem = new TodoItem() { Text = "test image", ImageName = media.Name };
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
//Upload image with HttpClient to the blob service using the generated item.SAS
using (var client = new HttpClient())
{
//Get a stream of the media just captured
using (var fileStream = await media.OpenStreamForReadAsync())
{
var content = new StreamContent(fileStream);
content.Headers.Add("Content-Type", media.ContentType);
content.Headers.Add("x-ms-blob-type", "BlockBlob");
 
 
using (var uploadResponse = await client.PutAsync(new Uri(todoItem.SAS), content))
{
//TODO: any post processing
}
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Run the application

  1. Hit F5 on the application and right click with your mouse to show the app bar
  2. Press the Take Photo button
  3. Observe that the SAS is returned from your Mobile Service
  4. Check your storage account now has the captured virtual High Five photo/video :)

Enjoy,
Nick

  • Pingback: How to upload an Image to Windows Azure Storage using Mobile Services | Nick Harris .NET

  • http://twitter.com/mattkrebs Matt Krebs

    Nick,

    Thanks for the post,. Having a little trouble getting it working however. Below is the response from trying to put the blob to my storage account? Any ideas?

    AuthenticationFailed

    Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:d9a70c20-e898-4ebe-a323-afd95899f931 Time:2013-01-16T22:17:44.0572618Z

    Signature did not match. String to sign used was rw 2013-01-18T22:39:31Z /rbalist/rbalist/38962510.jpeg 2012-02-12

  • http://twitter.com/mattkrebs Matt Krebs

    NM….just a bad url. Thanks for post, Great stuff. Got it working with Mono for Android.

  • http://twitter.com/cloudnick Nick Harris

    awesome sauce! :)

  • http://twitter.com/woutercx woutercx

    Hi, you said “When building device applications you should not store your storage account name and key within the device app”. But the key to the mobile service is still necessary in the app (App.xaml.cs). Because you do

    private IMobileServiceTable todoTable = App.MobileService.GetTable();

    var todoItem = new TodoItem() { Text = “test image”, ImageName = media.Name };

    await todoTable.InsertAsync(todoItem);

    sealed partial class App : Application
    {
    // This MobileServiceClient has been configured to communicate with your Mobile Service’s url
    // and application key. You’re all set to start working with your Mobile Service!
    public static MobileServiceClient MobileService = new MobileServiceClient(
    “https://pocmobieleintegratiedemo.azure-mobile.net/”,
    “SECRET KEY”
    );

    what do you think about that?
    Can’t this be abused if someone reverse engineers the app?

  • http://twitter.com/woutercx woutercx

    Thank you very much! Got it working on iOS / Xamarin.iOS (formerly Monotouch) !
    Took a while because I wanted to use await / async, but these commands were not available in the current mono version,
    so I had to use an alpha version.

  • nick.harris

    you should reach out to my team mate @chrisrisner – http://chrisrisner.com to discuss iOS dev w/ mobile services

  • nick.harris

    common approach here if embedding keys is to obfuscate your client code. Alternatively the next step up would be to update your mobile service to require ‘Only Authenticated Users’ rather then ‘Anyone with the application key’. With Only authenticated users you now have an authenticated user and can build your own authorization policy e.g a whitelist or blacklist using server scripts