Azure: Basic Implementation
1. Create a Storage Account
- Go to Storage accounts in the Azure Portal and click Create.
- Fill in the following:
- Resource group: Create a new one or choose an existing one.
- Storage account name: Enter a unique name (e.g.,
acmecorpuploads). Lowercase letters and numbers only. - Region: Choose the region closest to you.
- Go to the Advanced tab and set Allow Blob public access to Disabled.
- Click Review + create, then Create.
2. Create a Blob Container
- Open your new storage account and click Containers in the left menu.
- Click + Container.
- Enter a name (e.g.,
incoming-zips) and set Public access level to Private. - Click Create.
3. App Registration Setup
- Go to Azure Active Directory → App registrations → New registration.
- Enter a name (e.g.,
third-party-uploader) and leave all other settings as default. - Click Register.
- On the overview page, copy the Application (client) ID and the Directory (tenant) ID. You will need these later.
Add Redirect URIs (Callback URLs)
- Inside the app registration, go to Authentication → Add a platform.
- Select Web.
- Add the following URL:
https://api.hellowes.com/api/azure/callback - Click Configure.
Create a Client Secret
- Inside the app registration, go to Certificates & secrets → Client secrets → New client secret.
- Enter a description (e.g.,
upload-secret) and set an expiry date. - Click Add.
⚠️ Copy the secret value immediately. You will not be able to see it again.
4. Grant Access to the Container
This step gives the app registration permission to upload files to your container.
- Go to your storage account → Containers → click on your container (
incoming-zips). - Click Access Control (IAM) → Add role assignment.
- Select the role Storage Blob Data Contributor and click Next.
- Under Assign access to, select User, group, or service principal.
- Click Select members, search for
third-party-uploader, and select it. - Click Review + assign.
5. Share Credentials with the Third Party
Send the following values to the third party via a secure channel:
AZURE_TENANT_ID = Directory (tenant) ID from Step 3
AZURE_CLIENT_ID = Application (client) ID from Step 3
AZURE_CLIENT_SECRET = Secret value from Step 3
AZURE_STORAGE_ACCOUNT = Storage account name from Step 1
AZURE_CONTAINER_NAME = Container name from Step 2
⚠️ Do not send these over email in plain text.
6. Unpack Uploaded Zip Files
Azure Blob Storage does not automatically unpack zip files. After WCH uploads a zip to your private container, an Azure Function can extract it and write the files back to Blob Storage.
Recommended flow:
incoming-zip/project.zip
↓
Azure Blob Trigger Function
↓
site-files/index.html
site-files/assets/app.js
site-files/assets/styles.css
Use a separate container for extracted files:
incoming-zip # receives zip uploads
site-files # stores extracted files
The app registration from the previous steps should also be granted Storage Blob Data Contributor access to the destination container.
Azure Function Example
Install dependencies:
npm install @azure/storage-blob adm-zip mime-types
index.js
const { BlobServiceClient } = require("@azure/storage-blob");
const AdmZip = require("adm-zip");
const mime = require("mime-types");
module.exports = async function (context, zipBlob) {
const zipName = context.bindingData.name;
if (!zipName.endsWith(".zip")) {
context.log(`Skipping non-zip file: ${zipName}`);
return;
}
const connectionString = process.env.AzureWebJobsStorage;
const destinationContainerName = process.env.DESTINATION_CONTAINER || "site-files";
const blobServiceClient =
BlobServiceClient.fromConnectionString(connectionString);
const destinationContainer =
blobServiceClient.getContainerClient(destinationContainerName);
await destinationContainer.createIfNotExists();
const zip = new AdmZip(zipBlob);
const entries = zip.getEntries();
for (const entry of entries) {
if (entry.isDirectory) continue;
const filePath = entry.entryName.replace(/^\/+/, "");
const fileBuffer = entry.getData();
const blockBlobClient =
destinationContainer.getBlockBlobClient(filePath);
await blockBlobClient.uploadData(fileBuffer, {
blobHTTPHeaders: {
blobContentType:
mime.lookup(filePath) || "application/octet-stream",
},
});
context.log(`Uploaded extracted file: ${filePath}`);
}
context.log(`Finished extracting ${zipName}`);
};
function.json
{
"bindings": [
{
"name": "zipBlob",
"type": "blobTrigger",
"direction": "in",
"path": "incoming-zips/{name}",
"connection": "AzureWebJobsStorage"
}
]
}
App setting:
DESTINATION_CONTAINER=site-files
This keeps the upload flow simple: the third party only uploads the zip, and Azure handles extraction after upload.
This setup is just a simple way to get started, you are free to customize it however you want. EA/Webflow is not responsible for client side configurations.