How to upload images to Blob Storage using Serverless and Static Web Apps
Tags: serverless, tutorial, javascript, azure
Published: 2020-09-17 15:31:09

---
title: How to upload images to Blob Storage using Serverless and Static Web Apps
published: true
description: Explains how to generate SAS Keys with serverless for uploading images to blob storage
tags: serverless, tutorial, javascript, azure
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/wz6h0uvsisne7wkjgjfk.png
---

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](https://docs.microsoft.com/azure/static-web-apps/?WT.mc_id=servsept20-blog-alvidela), you are presented with the problem of how to authenticate users towards [Azure Blob Storage](https://docs.microsoft.com/azure/storage/blobs/?WT.mc_id=servsept20-blog-alvidela). Luckily, there's a solution for that. Add an [Azure Functions](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=servsept20-blog-alvidela) backend that takes care of generating [SAS Keys](https://docs.microsoft.com/azure/storage/common/storage-sas-overview?WT.mc_id=servsept20-blog-alvidela) 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](https://github.com/videlalvaro/upload_image).

## ServerlessSeptember

This article is part of [#ServerlessSeptember](https://aka.ms/ServerlessSeptember2020). 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/](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=servsept20-devto-cxaall).

## 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](https://azure.microsoft.com/free/?WT.mc_id=servsept20-blog-alvidela).

## Setting up Azure Blob Storage

Once you have registered your account on Azure, log in and create an [Azure Storage Account](https://docs.microsoft.com/azure/storage/common/storage-account-create?tabs=azure-portal&WT.mc_id=servsept20-blog-alvidela) 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](https://dev-to-uploads.s3.amazonaws.com/i/fltwmyth7q4vfl7j5mh3.png)

### Create a container

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

![New Container Image](https://dev-to-uploads.s3.amazonaws.com/i/z82yb4ht4qmw9jl8cv6j.png)

>[Follow this link to learn more on how to create a container](https://docs.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-portal?WT.mc_id=servsept20-blog-alvidela)

### Setup CORS

Now it's time to set up [CORS](https://docs.microsoft.com/rest/api/storageservices/cross-origin-resource-sharing--cors--support-for-the-azure-storage-services?WT.mc_id=servsept20-blog-alvidela) 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](https://www.w3.org/Security/wiki/Same_Origin_Policy) policy from browsers.

![CORS Setup](https://dev-to-uploads.s3.amazonaws.com/i/le35al56z560agfy6d7s.png)

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](https://docs.microsoft.com/azure/storage/common/storage-sas-overview?WT.mc_id=servsept20-blog-alvidela) 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](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=servsept20-blog-alvidela) 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](https://dev-to-uploads.s3.amazonaws.com/i/z8ujr3aanxy264flwcu8.png)

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:

```json
{
    "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](https://docs.microsoft.com/azure/developer/javascript/?view=azure-node-latest&WT.mc_id=servsept20-blog-alvidela):

```bash
 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:

```javascript
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:

```javascript
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](https://docs.microsoft.com/azure/azure-functions/functions-how-to-use-azure-function-app-settings?WT.mc_id=servsept20-blog-alvidela).

Take a look at the final [`index.js` file](https://github.com/videlalvaro/upload_image/blob/master/credentials/index.js) 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:

```html
<!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:

```javascript
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:

```bash
npm install webpack --save-dev
npm install webpack-cli --save-dev
```

Then run webpack by typing:

```bash
 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](https://dev-to-uploads.s3.amazonaws.com/i/fxq6t5ot9u99bwbqchl6.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/ve02a1j1x2n8k7tgq8sf.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/wz6h0uvsisne7wkjgjfk.png)

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

## What to do next

- Learn more about [Azure Functions](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=servsept20-blog-alvidela)
- Learn More about [Azure Static Web Apps](https://docs.microsoft.com/azure/static-web-apps/?WT.mc_id=servsept20-blog-alvidela)
- Learn more about [Azure Blob Storage](https://docs.microsoft.com/azure/storage/blobs/?WT.mc_id=servsept20-blog-alvidela)

>You can access the source code for this example here on GitHub: [videlalvaro/upload_image](https://github.com/videlalvaro/upload_image).