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:
- Setup Azure Blob Storage
- Create an Azure Functions API for your frontend
- Create the HTML/JS/CSS frontend for your app
- 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.
Create a container
Then navigate to your new storage account, select Containers below, and create a new container called images.
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.
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.
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:
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:
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:
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
- Learn More about Azure Static Web Apps
- Learn more about Azure Blob Storage
You can access the source code for this example here on GitHub: videlalvaro/upload_image.
Top comments (3)
Hi, for me this code "webpack --mode=development" doesn't work for me, I use it on the terminal of visual studio code. And I don't understand your line, where is the dist folder and main.js file?
Try to use following command in your root of node project:
node .\node_modules\webpack\bin\webpack.js --mode=development
Some comments may only be visible to logged-in visitors. Sign in to view all comments.