loading...
Cover image for How to upload images to Blob Storage using Serverless and Static Web Apps
Microsoft Azure

How to upload images to Blob Storage using Serverless and Static Web Apps

videlalvaro profile image Alvaro Videla ・9 min read

If you have an app that's accessed publicly via the browser, you want to restrict who's able to upload images to your storage backend, but by going the way of Azure Static Web Apps, you are presented with the problem of how to authenticate users towards Azure Blob Storage. Luckily, there's a solution for that. Add an Azure Functions backend that takes care of generating SAS Keys so your users can upload pictures directly to Azure Blob Storage without needing to create an account inside our systems.

You can access the source code for this example here on GitHub: videlalvaro/upload_image.

ServerlessSeptember

This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles from community members and cloud advocates are published every week from Monday to Thursday through September.

Find out more about how Microsoft Azure enables your Serverless functions at https://docs.microsoft.com/azure/azure-functions/.

What we are building

Here are the steps of what you need to do to accomplish that:

  1. Setup Azure Blob Storage
  2. Create an Azure Functions API for your frontend
  3. Create the HTML/JS/CSS frontend for your app
  4. Learn how to run your app in Visual Studio Code

To complete this tutorial you will need an Azure Account. Follow this link to signup for free.

Setting up Azure Blob Storage

Once you have registered your account on Azure, log in and create an Azure Storage Account called uploadimagesample (feel free to use any other name you prefer). You can do that by clicking the big plus button that says "Create a new resource", and then type "Storage Account" in the "Search the Marketplace" bar.

Storage Account

Create a container

Then navigate to your new storage account, select Containers below, and create a new container called images.

New Container Image

Follow this link to learn more on how to create a container

Setup CORS

Now it's time to set up CORS for your storage account. This will allow your app to send data from your own domain to Azure via HTTP, and circumvent the same-origin policy from browsers.

CORS Setup

As you can see from the image, you need to setup a * for Allowed origins, Allowed headers, and Exposed headers. Also select the HTTP verbs that you want to allow, and leave the Max age value as is. If you want later you can customize these values to fit your needs.

Now that you have set up Azure Blob Storage for image upload, it's time to create your Azure Functions API.

Creating the Serverless backend

For a client to be able to use anonymous authentication when sending data to Azure Blob Storage they would need to have a SAS key allowing them to perform their requests. You are going to create a serverless API that creates such a key, and sends it to the browser.

Create a new folder for the project called upload_image, and then open that folder in Visual Studio Code. Then press F1 and select Azure Functions: Create New Project. Choose JavaScript as the programming language, and finally HTTP trigger as the template for your new serverless function. The name for the function will be credentials, and the Authorization level Anonymous.

If you haven't done so, you need to install Azure Functions Core Tools to run the project locally.

Configure your storage connection

The last step to configure Azure Blob Storage is to tell Visual Studio Code how to connect to your storage account. For that go to Azure Portal and open the Access Keys section in your storage account. Grab the Connection String.

Connection String

Open the file called local.settings.json at the root of your project. There, edit the AzureWebJobsStorage key to include the storage connection string you just obtained from Azure Portal. See picture above. Keep in mind this information is private, so do not commit this file to git!

It should look like this, but with your actual connection string:

{
    "IsEncrypted": false,
    "Values": {
      "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=youraccountname;AccountKey=<SecretAccountKey>;EndpointSuffix=core.windows.net",
      "FUNCTIONS_WORKER_RUNTIME": "node"
    }
  }

Now it's time to implement your serverless function.

How to generate a SAS Key with Serverless

To generate a SAS key that can be used to authenticate to Azure anonymously, you need to install the Azure SDK for blob storage:

 npm install @azure/storage-blob

From the storage-blob SDK we are going to use the function generateBlobSASQueryParameters that creates a query string with the right authentication info that will let a client upload images to storage. That function requires a containerName, a set of permissions like read, write, etc., an expiresOn parameter for the SAS key, and a StorageSharedKeyCredential with the authentication info from your connection string. You are going to implement a function called generateSasToken that will take care of that process.

Open the index.js file from your credentials folder and add the following function at the bottom:

function generateSasToken(connectionString, container, permissions) {
    const { accountKey, accountName, url } = extractConnectionStringParts(connectionString);
    const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey.toString('base64'));

    var expiryDate = new Date();
    expiryDate.setHours(expiryDate.getHours() + 2);

    const sasKey = generateBlobSASQueryParameters({
        containerName: container,
        permissions: ContainerSASPermissions.parse(permissions),
        expiresOn: expiryDate,
    }, sharedKeyCredential);

    return {
        sasKey: sasKey.toString(),
        url: url
    };
}

The function generateSasToken takes a connectionString like the one you just copied into local.settings.json and parses it by calling the extractConnectionStringParts function to extract values like AccountKey or AccountName.

Then we create a StorageSharedKeyCredential by providing the accountName and accountKey you just extracted. In the case of the accountKey, you need to convert it to string using the base64 encoding, because it comes out as a Buffer from the parser function.

Next you need to set an expiry date for the generated key. So you can create a Date object and then set its time to two hours in the future. You can change the expiry time to adapt to your use case.

With everything in place you can call generateBlobSASQueryParameters from the @azure/storage-blob SDK and obtain the sasKey. Finally the return value of the function is the query string that includes our sasKey, and the URL that points to our storage instance.

Now it's time to implement the serverless function that will send the results from generateSasToken to the client. As you can see the function is quite basic:

module.exports = async function (context, req) {
    const permissions = 'c';
    const container = 'images';
    context.res = {
        body: generateSasToken(process.env.AzureWebJobsStorage, container, permissions)
    };
    context.done();
};

Here you can specify the storage permissions you are giving to the users, in this case just c that stands for create permissions. Then the container is called images, like the one you created above. From the process.env.AzureWebJobsStorage environment variable you can obtain the value that you set up in your local.settings.json file.

When you deploy this function to Azure, you need to setup the AzureWebJobsStorage variable in your app settings.

Take a look at the final index.js file in the repo to find the required imports for your serverless functions, and also to find the utils.js module that includes the extractConnectionStringParts function.

The next step is to implement the frontend part to contact your serverless API and upload the image to Azure Blob Storage.

Create the Static Web App Frontend

Start by creating an index.html file in the root folder, and add the following code to it:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Azure Blob Storage Image Upload</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
  </head>
  <body>
  <section class="section">
    <div class="container">
      <h1 class="title">Loading SASKey from the API: </h1>
      <pre id="name">...</pre>
      <br>
      <label for="image">Choose a profile picture:</label>
      <input type="file" id="image" name="image" accept="image/png, image/jpeg">
    </div>
  </section>
  <script src="./dist/main.js" type="text/javascript"></script>
    <script>
        (async function () {
            const {url, sasKey} = (await fetch("/api/credentials")).json();
            document.querySelector('#name').textContent = `SAS Key: ${sasKey}` + "\n" + `URL: ${url}`;
            function 'images', () {
                const file = document.getElementById('image').files[0];
                blobUpload(file, url, 'images', sasKey);
            };
            const fileInput = document.getElementById('image');
            fileInput.addEventListener("change", uploadFile);
        }())
    </script>
  </body>
</html>

Let's focus our attention on that <script /> segment. There you have an async function that will query the serverless API by calling fetch("/api/credentials"). That call will get you the url and sasKey values you generated earlier in the serverless function.

Then whenever the user selects a file, the change event from the file selector will fire, calling the uploadFile function. There we get the file information and pass it to the blobUpload function, so the file is uploaded to Azure Blob Storage. The function accepts the file object, a target URL, a container name, and SAS key.

To implement the blobUpload function, create a src folder and add an index.js file there. Then insert the following code:

const { BlockBlobClient, AnonymousCredential } = require("@azure/storage-blob");

blobUpload = function(file, url, container, sasKey) {
    var blobName = buildBlobName(file);
    var login = `${url}/${container}/${blobName}?${sasKey}`;
    var blockBlobClient = new BlockBlobClient(login, new AnonymousCredential());
    blockBlobClient.uploadBrowserData(file);
}

function buildBlobName(file) {
    var filename = file.name.substring(0, file.name.lastIndexOf('.'));
    var ext = file.name.substring(file.name.lastIndexOf('.'));
    return filename + '_' + Math.random().toString(16).slice(2) + ext;
}

The Azure Blob Storage Javascript SDK provides a BlockBlobClient class that comes with a uploadBrowserData method. You are going to use that to upload images to Azure Blob Storage.

To create a BlockBlobClient you'll need the login information, which consists of the URL including the query string that contains your SAS Key, and an AnonymousCredential instance to tell the BlockBlobClient how to authenticate to Azure.

The login information has the following format: ${url}/${container}/${blobName}?${sasKey}. url and sasKey was the data you got from the serverless function call. blobName is a randomly generated name for the uploaded image obtained by calling buildBlobName.

Now there's a very important detail in the require at the top of the file. You are requiring a node.js module in JavaScript code that will run in the frontend. For that to work you need to use Webpack to do the proper transformation.

Using the Azure Blob Storage SDK with Webpack

Install Webpack by running the following command in your projects root folder:

npm install webpack --save-dev
npm install webpack-cli --save-dev

Then run webpack by typing:

 webpack --mode=development

That command will extract the relevant files from the @azure/storage-blob SDK and make them compatible with the browser execution environment. The generated files will live in the dist folder.

Now you are ready to test the app and start uploading images to Azure Blob Storage.

Testing the app

Let's start by running the Azure Functions backend. Pressing F5 in Visual Studio Code should do the. You should see something like this:

Azure Functions Running CLI

To run the Static Web App locally you need to install the Live Server extension for visual studio code. Once it's installed, then press F1 and enter Open with Live Server. This will open a browser tab with the project running there:

Upload Form

Select an image from your computer and upload it to Azure Blob Storage. If all went well, we should see the image in the Storage Explorer:

Storage Explorer

Congrats! You just uploaded an image from an Azure Static Web App using Azure Functions to generate the SAS Key!

What to do next

You can access the source code for this example here on GitHub: videlalvaro/upload_image.

Posted on by:

videlalvaro profile

Alvaro Videla

@videlalvaro

Member of the @azureadvocates Team. @DuraznoConf Organizer. Coauthor of RabbitMQ in Action. Ex Apple - Ex RabbitMQ Core Dev. Opinions my own.

Microsoft Azure

Any language. Any platform.

Discussion

pic
Editor guide