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
.zipso that the scripts land inD:\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 = trueglobalValidation.unauthenticatedClientAction = "AllowAnonymous"identityProviders.azureActiveDirectory.registration.clientIdmatching your Entra appidentityProviders.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
appRoleAssignmentson 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. |




