A Logic App Standard proof-of-concept that validates Microsoft Entra Verified ID claims during account recovery.
This workflow is a low-code implementation of an Entra Verified ID claim-validation endpoint. It receives the Verified ID claim-validation callout from Entra, looks up the employee by UPN, compares the supplied documentNumber claim against the authoritative record, and returns a pass/fail decision — all using Logic App actions instead of compiled code.
┌─────────────┐ ┌────────────────────────┐ ┌─────────────────────┐
│ Entra │────▶│ ClaimValidation │────▶│ employees.json │
│ Recovery │ │ (Logic App Standard) │ │ (Blob / HTTP URL) │
│ Flow │◀────│ │◀────│ │
└─────────────┘ └────────────────────────┘ └─────────────────────┘
- Trigger — An HTTP
Requesttrigger (When_a_claim_validation_request_is_received) receives the Entra Verified ID claim-validation callout. - Download data —
Download_Employee_Dataperforms an HTTP GET against the employee data URL (an anonymous-read blob or any HTTP-hostedemployees.json). - Extract — The UPN and
documentNumberclaim are extracted from the request body. - Match — The workflow filters the employee list by UPN, then compares the supplied
documentNumberagainst the matched record'sDocumentId(case-insensitive). - Respond — Returns a Graph-shaped
pass/failresponse:- pass — UPN found and document number matches.
- fail (
documentNumber) — document number mismatch. - fail (
employeeNotFound) — no employee matched the UPN. - fail (
documentNumberMissing) — thedocumentNumberclaim was absent.
{
"data": {
"@odata.type": "microsoft.graph.onVerifiedIdClaimValidationCalloutData",
"verifiedIdClaimValidationResult": "pass"
}
}- Azure CLI (
az) signed in (az login) - An Azure subscription and a resource group
- (Local development only) Azure Functions Core Tools v4 and the Azure Logic Apps (Standard) VS Code extension
Deployment is four steps and the order matters:
flowchart LR
A["Step 1 - Provision infra<br/>Storage + Plan + empty Logic App"] --> B["Step 2 - Upload employees.json<br/>to Blob / HTTP"]
B --> C["Step 3 - Deploy workflow files<br/>zip to Logic App"]
C --> D["Step 4 - Test<br/>get callback URL and POST"]
⚠️ Don't stop after Step 1. The Deploy to Azure button (and the ARM template) create the infrastructure only — an empty Logic App. If you skip Steps 2-3 the Workflows blade shows No Workflows Found. Thedeploy.ps1script (Step 1, Option B) does Steps 1 and 3 for you — you still do Step 2 and Step 4.
Creates the Storage Account, the WS1 hosting plan, and the (empty) Logic App Standard site. Pick one option below.
The ARM template provisions the infrastructure only (Storage + WS1 plan + Logic App Standard). After it completes, continue to Step 2 and Step 3 — the Logic App is empty until you deploy the workflow files.
| Parameter | Description |
|---|---|
| Logic App name | Globally unique name for the Logic App Standard site. This is the only name you provide — the other two are derived from it. |
| App Service Plan name | Derived: <logic-app-name>-plan. Leave as default. |
| Storage account name | Derived: the Logic App name lowercased, with non-alphanumeric characters removed and trimmed to 24 chars. Leave as default. |
| Storage account type | Standard_LRS / Standard_GRS / Standard_RAGRS. |
| Logic App SKU | Hosting plan SKU (Workflow Standard tier): WS1 (default) / WS2 / WS3. See the note below. |
| Location | Azure region. |
| Employee data URL (optional) | HTTP(S) URL to employees.json. Surfaced as the EMPLOYEE_DATA_URL app setting. |
Why Logic App Standard? This tier was chosen over Consumption because it runs on the dedicated Azure Functions host (always-warm, no cold-start) for low-latency synchronous
pass/failresponses, supports file-based workflows in source control, and offers single-tenant isolation and VNet readiness suited to an identity flow. The plan SKU is parameterized (logicAppSku) and defaults to WS1 — scale up to WS2/WS3 for more vCPU/memory without changing anything else.
You can also deploy the template directly:
az deployment group create `
--resource-group <your-rg> `
--template-file ARMTemplate/template.json `
--parameters logicAppName=<unique-name> employeeDataUrl=<https-url-to-employees.json>deploy.ps1 is the recommended path. It recompiles the ARM template from infra/main.bicep (keeping the two in sync), provisions the infrastructure, and zip-deploys the workflow files in one step:
.\deploy.ps1Edit the configuration block at the top of deploy.ps1 ($subscriptionId, $resourceGroup, $location) before running.
Bicep is the source of truth.
ARMTemplate/template.jsonis generated frominfra/main.bicep(viaaz bicep build) so the Deploy to Azure button and the script never drift. To regenerate manually:az bicep build --file infra/main.bicep --outfile ARMTemplate/template.json
The workflow downloads its employee records over HTTP. Host SampleData/employees.json somewhere the Logic App can reach anonymously and set the URL (the EMPLOYEE_DATA_URL app setting, or update the Download_Employee_Data action URL in ClaimValidation/workflow.json).
# Example: upload to a blob container with anonymous read access
az storage blob upload `
--account-name <storageaccount> `
--container-name employeedata `
--name employees.json `
--file SampleData/employees.json `
--auth-mode loginIf you used Option A (ARM, infra only), package and deploy the workflow files to the Logic App. (Option B / deploy.ps1 already did this.)
Compress-Archive -Path host.json, connections.json, ClaimValidation -DestinationPath workflow.zip -Force
az functionapp deployment source config-zip `
--resource-group <your-rg> `
--name <logic-app-name> `
--src workflow.zipLogic App Standard runs on the Azure Functions host, so the deployment command is
az functionapp deployment source config-zip. This is a hosting detail of Logic App Standard — the POC itself contains no Azure Functions application code.
After deployment, get the workflow callback (trigger) URL and POST the sample request:
$callbackUrl = az rest --method POST `
--uri "https://management.azure.com/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<logic-app-name>/hostruntime/runtime/webhooks/workflow/api/management/workflows/ClaimValidation/triggers/When_a_claim_validation_request_is_received/listCallbackUrl?api-version=2024-04-01" `
--query 'value' --output tsv
Invoke-RestMethod -Method Post -Uri $callbackUrl `
-ContentType 'application/json' `
-InFile SampleData/sample-request.jsonExpected response for the sample request (jdoe@contoso.com / AB123456):
{ "data": { "@odata.type": "microsoft.graph.onVerifiedIdClaimValidationCalloutData", "verifiedIdClaimValidationResult": "pass" } }| Path | Purpose |
|---|---|
ClaimValidation/workflow.json |
The stateful workflow definition (trigger + claim-matching logic). |
host.json |
Logic App Standard host configuration (Workflows extension bundle). |
connections.json |
Managed API connections (empty — this POC uses an HTTP download, no connectors). |
local.settings.json |
Local runtime settings for the Azure Functions Core Tools / Logic Apps designer. |
ARMTemplate/template.json |
ARM template that provisions the Storage Account, App Service Plan (WS1), and Logic App Standard site. Generated from infra/main.bicep — do not edit by hand. |
ARMTemplate/createUiDefinition.json |
Portal UI definition for the Deploy to Azure button. |
infra/main.bicep |
Source of truth for the infrastructure. deploy.ps1 recompiles ARMTemplate/template.json from this on every run. |
deploy.ps1 |
End-to-end PowerShell deployment (infra + workflow zip-deploy). |
data/employees.json |
Runtime data file referenced by the workflow's HTTP download step. |
SampleData/employees.json |
Sample employee data to upload to Blob Storage (or any HTTP host). |
SampleData/sample-request.json |
Sample request payload for testing the workflow trigger. |
- Add records: edit
SampleData/employees.jsonand re-upload. - Match on additional claims: extend the
Check_DocumentNumber_Provided/Compare_DocumentNumberbranches inClaimValidation/workflow.jsonto compare more claim keys against their columns. - Use a real data source: replace the
Download_Employee_DataHTTP action with a connector (SQL, HTTP API, Excel Online, etc.) and add the corresponding entry toconnections.json.