Ok that might be a slight exaggeration but with some additional work and a native application running on the client it could be pretty close. The scenario I will be discussing here is a web based application for purchasing music.

I will leave the checkout process out of the discussion as that is not the interesting part. Just assume that a checkout exists (and it does, launch details soon) and you can buy digital products, music and other media.

All media is stored on a secure Container in Azure blob storage. In addition a public Container providing read-only access to music tracks for sampling whilst browsing also exists.

Ok, so what is the problem that we need to solve here?

  1. Users must be able to log on to a secure area and view purchases they have made.
  2. From any browser a user needs to be able to download individual tracks and have the appropriate Save As dialog displayed.
  3. Access to files must be secure and at no time should the user be aware of the URL they are downloading from. i.e.. the Url where the file actually exists.
  4. The URL that the user is downloading from must be unique and secure for every download and only valid for a short space of time.
  5. When a user attempts to download a file their identity must be authenticated and the tracks that they are downloading must be authorised as an item that they have purchased.

So those are the requirements, lets get stuck in to the code.

Item 1

This is an easy one and covered by the bog standard ASP.NET membership provider. The user signs on with their login and password and their details are validated.

bool result = Membership.ValidateUser(userName, password); if (result) { FormsAuthentication.SetAuthCookie(userName, bool.Parse(createPersistentCookie)); AssignCart(customer, cartId); }

If the user is valid they are redirected to a secure area of the site protected using the location element within a custom config file.

xmlversion="1.0"?><configuration><locationpath="login.aspx"><system.web><authorization><allowusers="*"/>authorization>system.web>location><system.web><authorization><denyusers="?"/>authorization>system.web>configuration>

Item 2

Ok now we are getting on to the juicy stuff. Once the user has securely logged in they are redirected to a page that lists all the digital items they have purchased. Each item contains a link to a Digital Download Handler which is a generic .ashx Handler File.

Now the trick is that we need to force the browser to display the Save As dialog on the OS that the browser is running on. To do this we need to add the “content-disposition” header to the request.

Full code for the download is shown below:

privatevoid StartDownload(HttpContext context, string fileUrl, string fileName) { fileName = fileName.Replace(" ", "_"); context.Response.Buffer = true; context.Response.Clear(); context.Response.AddHeader("content-disposition", "attachment; filename=" + fileName); context.Response.ContentType = "application/zip"; WebClient webClient = new WebClient(); byte[] resultBytes = webClient.DownloadData(fileUrl); context.Response.BinaryWrite(resultBytes); }

Item 3

Which brings us nicely on to item 3 of the list. The last 3 lines of code shown above use a url (fileUrl)that is unique and generated each time the user clicks on a download link. This is a Shared Access Signature that is unique and time sensitive. In this instance the url is only valid for a few minutes, allowing time for a download should the user be on a slow connection.

We create a WebClient and download the data from the url in to a byte array and finally do a BinaryWrite to the response stream sending the file to the user.

Item 4

The discussion above also covers item 4 but how do we create the Shared Access Signature. Well that is part of Azure and we will cover that now.

The code listing below covers the method for creating the Shared Access Signature. The GetContainer method is also included for completeness but it not part of the process for generating the Shared Access Signature.

publicstring GenerateSharedAccessSignatureForTrack(int trackId) { var blob = GetContainer("secure/").GetBlobReference(string.Format("{0}.mp3",trackId)); SharedAccessPolicy policy = new SharedAccessPolicy(); policy.Permissions = SharedAccessPermissions.Read; policy.SharedAccessExpiryTime = DateTime.UtcNow + TimeSpan.FromMinutes(sharedAccessLifeSpan); var sas = blob.GetSharedAccessSignature(policy); return blob.Uri.AbsoluteUri + sas; } private CloudBlobContainer GetContainer(string container) { // Get a handle on account, create a blob service client and get container proxy var account = CloudStorageAccount.Parse(string.Format("DefaultEndpointsProtocol=http;AccountName={0};AccountKey={1}", Properties.Settings.Default.AzureAccount, Properties.Settings.Default.AzurePrimaryKey)); var client = account.CreateCloudBlobClient(); return client.GetContainerReference(container); }

Once we have a reference to our blob, which in this case is a .mp3 file stored on the Azure file system in Blob Storage using the internal ID for the file, we create the Shared Access Policy.

The Policy only allows Read access and specifies an expiry time relative to UTC time. In this case the amount of time is stored in the readonly variable sharedAccessLifeSpan and is set to 5 minutes.

And finally we create our Shared Access Signature by calling the GetSharedAccessSignature method of the blob reference retrieved earlier and passing in the policy we just created.

This is then appended to the AbsoluteUri of the blob and voila, we have a unique and secure URI to access the file at for the next 5 minutes.

Keep in mind this is all happening inside the handler so the user never knows what the URL is that is being used for the download so they can’t share it with their friends. In fact you will not even see the Shared Access Signature even if you are doing a net trace with Firebug for example as the site (in this case) is also hosted in Azure so the process of accessing the secure file is occurring inside a Microsoft Datacentre. The only thing we are getting back is the raw binary stream that is the file.

In addition the handler is stored inside a secure folder so they must be signed in to gain access the .ashx file.

But what if by some chance we forgot to secure that file. Well never fear, this brings us on to item 5.

Item 5

So item 5 states “When a user attempts to download a file their identity must be authenticated and the tracks that they are downloading must be authorised as an item that they have purchased. “

Well we already did the authentication when we logged on but what if the .ashx file happens to be unsecure.

Never fear, we can place some code in the handler to check the users identity again and also validate that the file the user is attempting to download has in fact been purchased by them.

MembershipUser member = Membership.GetUser(); if (member null) { context.Response.ContentType = "text/plain"; context.Response.Write("0"); } else { using (Entities ctx = new Entities()) { Guid userId = (Guid)member.ProviderUserKey; var customer = (from c in ctx.Customers where c.UserId userId select c).SingleOrDefault(); if (customer != null) { #region Download Single Track if (trackId != null) { var purchase = (from t in ctx.ViewCustomerPurchasedTracks where t.CustomerId customer.CustomerId && t.TrackId trackId select t).SingleOrDefault(); if (purchase != null) { AzureStorage azure = new AzureStorage(); string fileUrl = azure.GenerateSharedAccessSignatureForTrack((int)trackId); StartDownload(context, fileUrl, string.Format("{0}.mp3", purchase.TrackTitle)); } else { context.Response.ContentType = "text/plain"; context.Response.Write("0"); } } #endregion } else { context.Response.ContentType = "text/plain"; context.Response.Write("0"); } } }

The first thing we do when entering the handler is get the currently logged on Member. If this returns null then we just exit the handler.

If not then we get the UserId GUID from the ProviderUserKey of the member and find the registered customer with this UserId.

Again if we don’t find a matching customer then we return from the handler with nothing.

However, if we find a match then we continue and check to see that the Member has purchased the track being asked for. If not then once again we just return with nothing.

But if they have then we generate the SharedAccessSignature from Azure as discussed and start the Binary Download.

And that’s it. The end to end process of downloading digital products in a secure manner using Azure.

BondiGeek