Azure: Basic Implementation

How WCH authenticates to Azure

The Azure Blob Storage destination in WCH has two Authentication Types:

  • SSO (recommended): you register an app in Microsoft Entra ID, then sign in through a Microsoft window that WCH opens. WCH stores the resulting token and refreshes it as needed. The account that signs in needs write access to the container.
  • Access Key: you paste one of the storage account's access keys into WCH. Simpler, but the key grants full access to the whole storage account and never expires unless you rotate it.

Steps 1 and 2 apply to both. Steps 3 and 4 are for SSO only.

1. Create a storage account

  1. Go to Storage accounts in the Azure Portal and click Create.
  2. Choose a Resource group, or create a new one.
  3. Enter a Storage account name, for example acmecorpuploads. Lowercase letters and numbers only.
  4. Choose the Region closest to you.
  5. On the Advanced tab, set Allow Blob public access to Disabled.
  6. Click Review + create, then Create.

2. Create a blob container

  1. Open your new storage account and click Containers in the left menu.
  2. Click + Container.
  3. Enter a name, for example incoming-zips, and set Public access level to Private.
  4. Click Create.

3. Register an app for SSO

  1. Go to Microsoft Entra ID, then App registrations, then New registration.
  2. Enter a name, for example wch-uploader, and leave the other settings as default.
  3. Click Register.
  4. On the overview page, copy the Application (client) ID and the Directory (tenant) ID. You will enter these in WCH.

Add the redirect URI

The SSO sign-in sends the user back to WCH, so the app registration must allow WCH's callback address.

  1. Inside the app registration, go to Authentication, then Add a platform.
  2. Select Web.
  3. Add this URL: https://api.hellowes.com/api/azure/callback
  4. Click Configure.

Create a client secret

  1. Inside the app registration, go to Certificates & secrets, then Client secrets, then New client secret.
  2. Enter a description, for example wch-secret, and set an expiry date.
  3. Click Add.

Copy the secret value immediately. Azure shows it once. When it expires, create a new one and update the destination in WCH.

4. Grant write access to the container

SSO uses the permissions of the person who signs in, so the signing-in user needs write access to the container. Granting the app registration the same role is a good idea for consistency.

  1. Go to your storage account, then Containers, and open your container.
  2. Click Access Control (IAM), then Add role assignment.
  3. Select the role Storage Blob Data Contributor and click Next.
  4. Under Assign access to, select User, group, or service principal.
  5. Click Select members, pick the user who will sign in to WCH and the app registration, and select them.
  6. Click Review + assign.

5. Add the destination in WCH

  1. Open your project, then Extensions, then Deployments.
  2. Click + Add Deployment and choose Azure Blob Storage.
  3. Choose an Authentication Type.

For SSO

  • Tenant ID: the Directory (tenant) ID from step 3.
  • Client ID: the Application (client) ID from step 3.
  • Client Secret: the secret value from step 3.
  • Redirect URI: leave empty to use the default WCH callback.
  • Storage Account Name: from step 1.
  • Container Name: from step 2.

When you save, a Microsoft sign-in window opens. Sign in with the user you granted access in step 4 and approve access to Azure Storage.

For Access Key

  • Access Key: from the storage account's Security + networking, then Access keys.
  • Storage Account Name and Container Name: as above.

Both types

  • File Name: the blob name to write, by default project.zip. Tick Use project name to name it after your project.
  • Is Production?: turn on for your production target. A project can have one production Azure destination.

If you are sharing these values with someone else who will enter them, send them over a secure channel, never in plain-text email.

6. Unpack the uploaded zip

Azure Blob Storage does not unpack zip files. After WCH uploads the archive to your private container, an Azure Function can extract it and write the files to a second container that your site is served from.

incoming-zips/project.zip
        ↓
Azure Blob Trigger Function
        ↓
site-files/index.html
site-files/about/index.html
site-files/404/index.html
site-files/css/site.123abc.css

Use a separate container for extracted files:

  • incoming-zips receives zip uploads
  • site-files stores the extracted site

WCH stores every page as a folder with an index.html inside, and the 404 page at 404/index.html. Configure your static website hosting or CDN to serve those index files for folder paths and to use 404/index.html as the error page.

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

The function overwrites files that changed but does not delete files that no longer exist in your site. Clean those up separately if it matters for your site.

This setup is a simple way to get started. You are free to customize it. Edgar Allan and Webflow are not responsible for client-side configuration.