1. Home
  2. Technical Documentation
  3. Security – Azure Functions Authentication

Security – Azure Functions Authentication

Warewolf Lightweight Execution runs as an Azure Functions v4 isolated worker app. Access to its /Secure/* HTTP endpoints is protected by Microsoft Entra ID combined with App Service Easy Auth (V2). /Services/* endpoints use function-key auth.

Configure-WwExecutionAuth.ps1 provisions that Microsoft Entra ID authentication and Azure Easy Auth for the Warewolf Lightweight Execution Engine’s Azure Function App. A single script call creates or upgrades the Entra app registration, exposes the required API scopes, declares app roles that mirror your secure.config groups, assigns users to those roles, rotates the client secret when needed, wires all required Function App settings, and enables the Easy Auth Microsoft provider — in that order, idempotently and crash-safely. The script is safe to re-run against an existing app; it upgrades in place without destroying anything.

Download the Deployment Scripts. Download the Warewolf Azure Execution Engine deployment scripts from warewolf.io/release-notes.php and extract the .zip so that the scripts land in D:\ExecutionEngine\Scripts. All examples in this article assume that path.

This script is invoked by Deploy-WwExecutionEngine.ps1 (via -AuthConfigPath) as part of an end-to-end deploy. It can also be run standalone — directly, or to update auth on an already-deployed Function App without redeploying the package.

Prerequisites

Tool Minimum version Notes
PowerShell 7.0+ (latest recommended) The script requires PowerShell 7.0+; it uses strict-mode features and modern syntax. Windows PowerShell 5.1 will not run it. Install via winget install Microsoft.PowerShell.
Azure CLI (az) 2.55.0+ All provisioning runs through az. You must be logged in before running the script; the examples explicitly set the target subscription. Install via winget install Microsoft.AzureCLI.

The target Function App must already exist before running this script. To create the Function App alongside auth provisioning in a single call, use Deploy-WwExecutionEngine.ps1 -AuthConfigPath instead.

Required roles & privileges

Auth provisioning touches two independent planes. Roles on one plane do not grant permissions on the other.

Plane Role Why
Azure RBAC Contributor on the Function App (or its resource group) Writes Function App settings and Easy Auth configuration.
Microsoft Entra (directory) Application Administrator (or Graph Application.ReadWrite.All) Creates/updates the Entra app registration, service principal, OAuth scope, app roles, and client secret.

Grant commands (an admin runs these for the deploying user):

# Resolve the target user's object ID
$UserOid = az ad user show --id 'deployer@yourtenant.com' --query id -o tsv
$Sub = '<your-subscription-id>'
$Rg = '<your-resource-group>'

# Azure RBAC — Contributor on the resource group
az role assignment create --assignee $UserOid --role 'Contributor' --scope "/subscriptions/$Sub/resourceGroups/$Rg"

# Entra directory role — Application Administrator
az rest --method POST `
--url 'https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments' `
--headers 'Content-Type=application/json' `
--body "{`"principalId`":`"$UserOid`",`"roleDefinitionId`":`"9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3`",`"directoryScopeId`":`"/`"}"

The admin running the Entra role assignment must themselves be Privileged Role Administrator or Global Administrator.

Auth config JSON

The script reads group and user configuration from a JSON file. Create this file before running the script (a ready-to-edit template ships alongside the scripts as Deploy-WwExecutionEngine.authconfig.example.json):

{
"_comment": [
"Copy the shipped example and edit the values.",
"Client apps / managed identities belong in GroupPermissions only, not UserAssignments."
],
"GroupPermissions": {
"Warewolf_Developers": [],
"Warewolf_Operators": [],
"Warewolf_Administrators": [],
"Warewolf_ClientApps": []
},
"UserAssignments": [
{ "Upn": "user1@theunlimited.co.za", "Group": "Warewolf_Administrators" },
{ "Upn": "user2@theunlimited.co.za", "Group": "Warewolf_Developers" },
{ "Upn": "user3@theunlimited.co.za", "Group": "Warewolf_Operators" }
]
}

Key Description
GroupPermissions A map of group name to an array of additional Permission.* roles auto-assigned alongside the group role. Pass an empty array ([]) for group-only access. Each key becomes an Entra app role, can be assigned to users or app-only clients, and must match a WindowsGroup entry in your secure.config.
UserAssignments An array of UPN-to-group mappings. Each entry assigns a user to one of the groups declared in GroupPermissions. This section is UPN-only; client apps and managed identities are assigned separately to an app role from GroupPermissions. A user’s UPN must already exist as an Entra ID user in your tenant.

Group names must match your secure.config. The script creates Entra app roles whose value is derived directly from the group name (spaces and most punctuation are replaced with _). The Warewolf engine maps the role claim it receives in the JWT back to a WindowsGroup in secure.config to resolve permissions. If the names differ, the role is present in the token but resolves to no permissions.

Step-by-step

Step 0 — Log in and set your values

Run PowerShell 7.0+. Log in, then set all your deployment values once in the session:

az login

$SubscriptionId = '<your-subscription-id>'
$TenantId = '<your-tenant-id>'
$ResourceGroup = '<your-resource-group>'
$FunctionAppName = '<your-function-app-name>'
$AuthConfigPath = 'D:\ExecutionEngine\Scripts\Deploy-WwExecutionEngine.authconfig.json'

az account set --subscription $SubscriptionId
cd D:\ExecutionEngine\Scripts

Step 1 — Run the script

Interactive run — prompts for any values not supplied as parameters.

$authCfg = Get-Content $AuthConfigPath -Raw | ConvertFrom-Json -AsHashtable

.\Configure-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-TenantId $TenantId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-GroupPermissions ([hashtable]$authCfg.GroupPermissions) `
-UserAssignments @($authCfg.UserAssignments) `
-SkipSmokeTest

Non-interactive run — suitable for CI/CD pipelines.

$authCfg = Get-Content $AuthConfigPath -Raw | ConvertFrom-Json -AsHashtable

.\Configure-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-TenantId $TenantId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-GroupPermissions ([hashtable]$authCfg.GroupPermissions) `
-UserAssignments @($authCfg.UserAssignments) `
-SkipSmokeTest `
-NonInteractive

Dry run — prints the full plan without touching Azure.

.\Configure-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-TenantId $TenantId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-WhatIfOnly

Force secret rotation — use after a suspected secret compromise or on a schedule.

.\Configure-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-TenantId $TenantId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-RotateSecret `
-NonInteractive

Step 2 — What the script does

The script runs the following stages in order. Every stage is idempotent — re-running the script upgrades existing objects in place.

Stage Name What happens
0 Pre-flight Validates the supplied targeting values, sets the active Azure subscription, verifies the Function App exists, and exits early if -WhatIfOnly is set.
1 Entra app registration Creates a new Entra app registration named <FunctionAppName>-auth (or upgrades the existing one). Sets the redirect URI to https://<FunctionAppName>.azurewebsites.net/.auth/login/aad/callback. Sign-in audience is AzureADMyOrg (single tenant).
2 Implicit grant — ID token Enables enableIdTokenIssuance=true on the registration. Required for Easy Auth’s hybrid flow; without it, browser sign-in stalls at the callback with AADSTS700054.
3 Expose API Sets identifierUris=[api://<clientId>] on the app. This is the resource identifier used by the engine’s audience validation and by OAuth clients acquiring tokens.
3b Expose user_impersonation scope Adds the user_impersonation delegated permission scope to the app if absent. Without it, token acquisition via az account get-access-token --resource api://<clientId> or MSAL fails with AADSTS650057. Idempotent — the existing scope GUID is preserved so prior consents stay valid.
4 App roles Declares one Entra app role per group in GroupPermissions. Existing role GUIDs are preserved (so live appRoleAssignments stay valid). In interactive mode, prompts whether to merge (add new roles only) or replace (disable stale roles) when the desired set differs from what is on the app.
5 Service principal Creates the service principal for the app registration if it does not exist. Uses Graph retry with exponential backoff to handle eventual-consistency lag after a fresh app creation.
6 User role assignments Assigns each user in UserAssignments to their group’s app role (and any additional Permission.* roles). Skipped with -SkipUserAssignment. Safe to re-run — already-assigned roles are skipped. Never aborts the whole stage on one bad row: a missing/typo’d UPN is warned and that row is skipped, and the remaining assignments still proceed.
7 Client secret Creates a new client secret when: -RotateSecret is passed; no existing credential has more than 30 days remaining; or the MICROSOFT_PROVIDER_AUTHENTICATION_SECRET Function App setting is missing (the old value cannot be recovered). Otherwise keeps the existing credential.
8 Function App settings Writes the Entra tenant/audience/secure.config app settings, plus the client-secret setting when a new secret is created or rotated (must precede Easy Auth, which references them by name).
9 Easy Auth Migrates the Function App from Easy Auth V1 to V2 if needed, then configures the Microsoft (Entra) provider and enables the platform with AllowAnonymous action and token store enabled.
10 End-to-end verification Cross-checks Easy Auth state, implicit grant flag, user_impersonation scope, all four required app settings, and the redirect URI. Throws with a clear error list if anything is wrong.
11 Smoke test (optional) HTTP probes the live Function App: expects HTTP 200 from a /Public/Hello World.json call and HTTP 401/302 from a /Secure/ call without a token. Skipped with -SkipSmokeTest. Failures here are warnings, not throws — the smoke test assumes the function package is already deployed.
12 Persist outputs Writes a summary JSON to Configure-WwExecutionAuth.output.json in the script directory. Contains the client ID, object IDs, audience, issuer, and role/user assignment summary.

Step 3 — Verify

Stage 10 runs automatically on a full run. -WhatIfOnly only previews the resolved inputs and exits before verification. For a manual spot-check, inspect the Easy Auth state directly:

az webapp auth show --name $FunctionAppName --resource-group $ResourceGroup -o json

The output should show:

  • platform.enabled = true
  • globalValidation.unauthenticatedClientAction = "AllowAnonymous"
  • identityProviders.azureActiveDirectory.registration.clientId matching your Entra app
  • identityProviders.azureActiveDirectory.validation.allowedAudiences[0] = "api://<clientId>"
  • login.tokenStore.enabled = true

Function App settings written by the script

Stage 8 writes the following settings to the Function App’s configuration. The engine reads these at startup.

Setting Value Purpose
WAREWOLF_ENTRA_TENANT_ID Your Entra tenant GUID Tells the engine which tenant to validate tokens against.
WAREWOLF_ENTRA_AUDIENCE api://<clientId> The expected aud claim in bearer tokens presented to /Secure/* endpoints.
WAREWOLF_SECURE_CONFIG D:\home\site\wwwroot\secure.config (default) Path to the secure.config file on the Function App filesystem. Override with -SecureConfigMountPath.
MICROSOFT_PROVIDER_AUTHENTICATION_SECRET Client secret value (written only when a new secret is created/rotated) The Easy Auth Microsoft provider reads the client secret from this app setting by name. The secret value itself is never echoed to the console or logs.

Easy Auth configuration reference (authsettingsV2.json)

The script ships with authsettingsV2.json as a documentation reference for the Easy Auth V2 configuration it applies. You do not deploy this file during a normal run; Stage 9 applies the equivalent settings directly via Azure CLI/ARM calls. It is included for reference if you need to apply the configuration manually via the ARM REST API:

az rest --method PUT `
--url "https://management.azure.com/subscriptions/<SUB>/resourceGroups/<RG>/providers/Microsoft.Web/sites/<APP>/config/authsettingsV2?api-version=2022-03-01" `
--body @authsettingsV2.json

Before applying the file manually, replace <TENANT_ID> and <CLIENT_ID> with values from the script output, and ensure MICROSOFT_PROVIDER_AUTHENTICATION_SECRET is set in the Function App’s application settings.

Parameter reference

Targeting

Parameter Required Default Description
-SubscriptionId No prompted if omitted Azure subscription GUID. In -NonInteractive mode, pass a real value explicitly.
-TenantId No prompted if omitted Microsoft Entra tenant GUID. In -NonInteractive mode, pass a real value explicitly.
-ResourceGroupName No prompted if omitted Resource group containing the Function App.
-FunctionAppName No prompted if omitted Name of the existing Function App to configure.
-EntraAppDisplayName No <FunctionAppName>-auth Display name for the Entra app registration. Auto-derived from -FunctionAppName when omitted.
-SecureConfigMountPath No D:\home\site\wwwroot\secure.config Value written to the WAREWOLF_SECURE_CONFIG app setting. Change this if your secure.config is mounted at a different path.

Auth configuration

Parameter Required Default Description
-GroupPermissions No @{} (empty) A hashtable mapping group names to arrays of additional Permission.* roles. Pass an empty hashtable or populate interactively. Each key becomes an Entra app role.
-UserAssignments No @() (empty) An array of @{ Upn = '...'; Group = '...' } hashtables mapping user UPNs to groups from -GroupPermissions.

Stage flags

Parameter Default Description
-RotateSecret off Force a fresh client secret even if a valid, non-expiring one exists.
-SecretLifetimeYears 1 Validity period in years for any new client secret. Minimum 1, maximum 2 (Entra cap).
-SkipUserAssignment off Skip Stage 6 (user role assignments). Use when you have limited Graph permissions or want to manage user assignments separately.
-SkipSmokeTest off Skip Stage 11 (HTTP probe). Use on first run before the function package is deployed.
-WhatIfOnly / -DryRun off Print the resolved configuration and planned changes, then exit without making any changes. Safe for CI/CD pre-merge checks.
-ReplaceAppRoles off In Stage 4, disable and remove all existing app roles not in the desired set without prompting. In interactive mode the operator is always asked; in -NonInteractive mode without this flag, new roles are added and stale roles are left in place (safe for re-runs).
-ReplaceUserAssignments off In Stage 6, clear all existing role assignments for each listed user before re-assigning the desired set. Without this flag, only missing assignments are added.
-UseManagedIdentity off Suppresses additional client-secret rotations beyond the minimum required for Easy Auth’s confidential client flow. Recommended for production tenants that prefer managed identity over secret rotation.
-NonInteractive off Skip all interactive prompts. Required values not supplied as parameters throw early. Use in CI/CD pipelines.
-LoadFunctionsOnly off Test hook. Dot-sources the script to define its helpers and returns before any interactive prompt or cloud/Graph action (used by the Pester suite Tests/Configure-WwExecutionAuth.Tests.ps1). Not for normal runs.

Cleaning up

Cleanup-WwExecutionAuth.ps1 reverses everything Configure-WwExecutionAuth.ps1 set up. It is targeted and non-destructive: only the resources the configure script created are removed. The Function App itself, the resource group, storage, and all user accounts are left untouched.

What it removes (in order):

  • Stage 3 — Disables Easy Auth on the Function App (platform.enabled = false)
  • Stage 4 — Removes the four Function App settings: WAREWOLF_ENTRA_TENANT_ID, WAREWOLF_ENTRA_AUDIENCE, WAREWOLF_SECURE_CONFIG, MICROSOFT_PROVIDER_AUTHENTICATION_SECRET
  • Stage 5 — Deletes the Entra app registration, which cascades automatically to: the service principal, all user appRoleAssignments on that SP, all app roles and OAuth scopes, and all client secrets

# Preview (dry run)
.\Cleanup-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-WhatIfOnly

# Execute (prompts for confirmation)
.\Cleanup-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName

# Execute without prompt (use with care)
.\Cleanup-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-Force

# Keep the Entra app (only undo Function App settings + Easy Auth)
.\Cleanup-WwExecutionAuth.ps1 `
-SubscriptionId $SubscriptionId `
-ResourceGroupName $ResourceGroup `
-FunctionAppName $FunctionAppName `
-KeepEntraApp -Force

Troubleshooting

Symptom Likely cause Fix
Browser sign-in stalls at /.auth/login/aad/callback with AADSTS700054 enableIdTokenIssuance is not enabled on the Entra app registration. Re-run the script; Stage 2 sets this flag. Verify with: az ad app show --id <clientId> --query web.implicitGrantSettings -o json
az account get-access-token --resource api://<clientId> fails with AADSTS650057 The user_impersonation delegated scope is missing from the app registration. Re-run the script; Stage 3b adds the scope. Verify with: az ad app show --id <clientId> --query api.oauth2PermissionScopes -o json
Secured route returns HTTP 401 or HTTP 500 with a valid token The token’s role claim does not match a group in secure.config, or the group has insufficient permissions. Confirm the group name in GroupPermissions exactly matches the WindowsGroup in secure.config. Check the View and Execute flags on that group’s entry. See Security — Secure.config.
Stage 9 fails with “Cannot use auth v2 commands when the app is using auth v1” The Function App was created with Easy Auth V1 and the migration step encountered an older az CLI build. Update az to 2.55.0+ (az upgrade). The script auto-migrates V1 → V2; if it still fails, run az webapp auth config-version upgrade --name <app> --resource-group <rg> manually, then re-run.
Stage 10 fails with “app setting ‘MICROSOFT_PROVIDER_AUTHENTICATION_SECRET’ is missing” The script ran previously without creating a secret (e.g. a partial run), or the setting was deleted manually. Re-run with -RotateSecret; Stage 7 will create a new secret and Stage 8 will write the setting.
Stage 6 reports “role ‘<groupName>’ not found on SP” Graph eventual consistency: the app role was just created and has not replicated to the SP yet. Wait 30–60 seconds and re-run. The script uses retry logic, but very large tenants may need more time.
Stage 6 warns “user ‘<upn>’ not found / lookup failed” or “lookup returned no object” and continues A UPN in UserAssignments is missing/typo’d or doesn’t exist in the tenant. Not a hard failure — that one row is skipped and the remaining assignments still proceed. Fix the UPN in your -AuthConfigPath JSON and re-run to pick up the skipped user (safe to re-run — already-assigned roles are skipped).
Smoke test probe returns an unexpected status code The function package is not yet deployed, or a Hello World workflow does not exist in /Public. Deploy the package first, then re-run with -SkipSmokeTest:$false. Smoke-test failures are warnings, not hard errors — auth configuration is still applied correctly.

 

FacebookTwitterLinkedInGoogle+Email
Updated on July 30, 2026

Was this article helpful?

Related Articles

Enjoying Warewolf?

Write a review on G2 Crowd
Stars