Zero Trust on Azure

Your network can be hardened end to end and still sit one compromised account away from a breach. That sentence is the whole case for Zero Trust, and it explains why the generic Zero Trust architecture picture keeps circulating. Users and devices at the top, an identity provider, MFA, a policy engine, a policy enforcement point, segmentation, protected resources, and a monitoring column on the side. Never trust, always verify, least privilege. This post is about Zero Trust on Azure: which Microsoft service does each job, and where the neat boxes stop matching how the platform behaves.

I like the generic picture. It is the NIST SP 800-207 model with icons, and it summarizes a modern access path fairly. It does not tell you what to switch on in an Azure tenant, though. So I redrew it with Azure icons, one service per box, and wrote down what I had to change to make it accurate.

Zero Trust on Azure: the generic architecture mapped to Microsoft Entra, Intune, Global Secure Access, Azure networking, and Microsoft Sentinel.

Zero Trust on Azure: the same spine, with names on it

The generic flow has eleven steps. Here is what each one is called in a Microsoft tenant.

Steps 1 to 6: identity and policy

  1. Verify users. Employees, admins, vendors and workloads are all Microsoft Entra ID identities. Vendors come in through External ID (B2B), and workloads through app registrations and managed identities. That last group matters more than the picture suggests, and I will come back to it.
  2. Check devices. Device trust is an Intune compliance policy plus a Defender for Endpoint risk score, which Entra receives as a device claim. Patch status, encryption, OS health and firewall state are compliance checks, not network checks.
  3. Verify identity. One identity provider: Entra ID. In a hybrid tenant, Connect Sync or Cloud Sync brings in the on-premises accounts, and the Active Directory authority boundary stays where it was.
  4. Strengthen authentication. MFA, authentication strengths, passkeys and FIDO2 are Entra features. The policy that demands them lives in the next box.
  5. Evaluate access. Conditional Access takes the signals: user, group, device compliance, location, sign-in risk and user risk, client app, and the sensitivity of the target app.
  6. Make policy decisions. Also, Conditional Access. Grant, block, require MFA, require a compliant device, require an authentication strength, or apply session controls.

Steps 7 to 11: enforcement, isolation and visibility

  1. Enforce access. This is where the picture and the platform part ways. More below. The candidates are Global Secure Access, Front Door and Application Gateway with WAF, API Management validate-jwt, the application itself, and Bastion.
  2. Isolate workloads. Virtual networks, network security groups, application security groups, Azure Firewall, private endpoints, and private DNS.
  3. Protect resources. App Service, Functions, API Management, Azure SQL, Storage, Key Vault, and for the Active Directory box, Entra Domain Services or domain controllers behind Entra Private Access.
  4. Monitor continuously. Microsoft Sentinel for SIEM and SOAR, Defender for Cloud for posture and workload protection, ID Protection for risky sign-ins and users, Log Analytics for everything the rest emits.
  5. Build the foundation. Key Vault and customer managed keys, Purview labels and DLP, managed identities, Azure Policy, PIM, backup, and a landing zone you segment before the first workload lands.

That mapping is useful on its own. Architecture reviews ask me for it regularly, usually from someone with the generic picture, asking, “so what do we actually buy?” But three of the boxes hide something, and that’s why I’m writing this post.

Policy engine and policy administrator share one box

NIST 800-207 separates the policy engine (decides) from the policy administrator (tells the enforcement point what it decided). The generic picture keeps them apart and gives the administrator its own job list: create a secure session, issue an access token, enforce policy decisions, monitor the session, log and audit.

Zero Trust on Azure does not split them into two services. Conditional Access is Microsoft’s policy engine, and the token service that acts on its verdict is the same Entra ID. The decision and the administration happen in one evaluation at token issuance. There is no separate component you can point at and call the PA.

That collapse is convenient, and it has a cost. Because the decision binds to token issuance, the decision also binds to token lifetime. Which brings me to the enforcement point.

The enforcement point in Zero Trust on Azure is not one gateway

The picture draws a single Policy Enforcement Point, a ZTNA gateway or reverse proxy, and lists four jobs for it: verify every request, decrypt and inspect traffic, enforce least privilege, block unauthorized access.

On Azure, I count at least six places that do some of that for some traffic, and none of them does all of it for all traffic.

  • Entra ID itself, at token issuance. Conditional Access enforces here, and for most SaaS and Microsoft 365 traffic this is the only enforcement point there is.
  • Global Secure Access, the ZTNA layer. Private Access fronts internal apps and, since January 2026, domain controllers, so Kerberos traffic can finally sit behind a Conditional Access policy. Internet Access is the secure web gateway. TLS inspection for it is still in preview as I write this, so “decrypt and inspect traffic” is a roadmap item for part of the stack, not a given.
  • Front Door and Application Gateway, with WAF policies, for public web entry points.
  • API Management, where validate-jwt checks the token the app will later trust.
  • The application, which validates the token again and applies its own authorization. Microsoft’s own guidance says Conditional Access “doesn’t prevent the app from blocking access on its own”. The app is an enforcement point whether you designed it that way or not.
  • Bastion, for the administrative sessions that never pass through any of the above.

The generic picture is not wrong to draw one box. It is wrong if you read it as one product. Every Zero Trust review I have done ended with a table of enforcement points and the traffic each one does not see. The gaps in that table are the architecture.

“Verify every request” means “verify every token issuance”

This is the part I most wanted to write down, because it surprises people when they measure it.

Entra evaluates Conditional Access when it issues a token. Access tokens live 60 to 90 minutes by default. Between issuances, the resource trusts the token, and a policy change, a password reset, a disabled account, or a risk event does nothing to a token already in use. “Verify every request” at the resource means “check the signature and expiry of a token that Entra verified up to an hour and a half ago”.

Continuous Access Evaluation closes that gap, but only for resources and clients that support it. In my earlier post, Microsoft Entra ID under the hood, I measured this on a test tenant. A CAE-aware client received a 1439-minute token, and the resource rejected it four seconds after I revoked its sessions. The non-CAE client next to it kept a 65-minute token and kept receiving 200 responses until it expired. Same tenant, same policy, same user. The only difference was whether the client and resource spoke CAE.

So the “Revoke” decision in the picture’s policy engine box is real, but it depends on the plumbing. For your own APIs on App Service, Functions, or API Management, CAE does nothing unless you build the claims challenge handling yourself. The honest version of the diagram puts a footnote under Revoke, and that is what mine does.

Where this Zero Trust on Azure mapping is the wrong answer

  • If most of your estate is not on Azure. Conditional Access and Global Secure Access still apply to a mixed estate, but the segmentation and protected resources rows are Azure-specific. For AWS or on-premises workloads, Entra Private Access gives you an enforcement point in front of them, not segmentation behind them.
  • If you have no Intune. Device compliance is the second strongest signal Conditional Access has. Without a device management platform feeding it, “check devices” collapses to “is the device Entra joined”, a weaker test than the picture implies.
  • If you are on Entra ID Free or P1 only. Sign-in risk and user risk conditions need P2. Global Secure Access needs the Entra Suite or standalone licenses. The mapping above assumes you have them, and many tenants do not.
  • If your apps cannot validate tokens. Legacy applications behind an App Proxy or Private Access get an enforcement point in front of them and nothing inside them. That is still better than nothing, and it is not Zero Trust at the application layer.
  • If nobody reads Sentinel. The monitoring column feeds risk back into policy only if someone tunes analytics rules and acts on the incidents. A workspace that ingests everything and alerts no one is a cost, not a control.

The question at the bottom of the picture

The generic post ends with a good question: if an attacker compromised one employee’s account today, how much of your network could they reach?

On Azure, the answer comes from five smaller questions. Does the compromised identity hold standing privileged roles, or does PIM activate them with a window? Does Conditional Access require a compliant device, so a stolen credential on an unmanaged laptop stops at step five? Do the resources sit on private endpoints, behind network security groups that only admit the subnet that should talk to them? Do the workloads that matter speak CAE, so a revoke lands in seconds rather than at the next token expiry? And can Sentinel tell you any of this happened before the attacker finishes?

I have not yet met a tenant where all five answers were yes. The mapping in this post is how I find out which ones are no.

Zero Trust is not a product you install. Zero Trust on Azure is about fifteen services configured to agree with each other, and the diagram only becomes true once they do. If you want the same treatment for the identity layer alone, Microsoft Entra ID goes deeper under the hood, and the API Management for AI workloads series covers the validate-jwt enforcement point in detail.

Azure Functions Behind API Management: What the Happy Path Diagram Leaves Out

Recently, I noticed another Azure architecture infographic on LinkedIn. Four boxes, left to right: clients, API Management, Function App, backend services. Underneath it, a tidy line. Function App handles the code, API Management handles the control. Together they deliver powerful and secure APIs.

(Source: LinkedIn post)

Nothing in that diagram is wrong. However, it’s not the whole story.

I have reviewed this pattern several times, in reference architectures, in project designs, and in my own work. The four boxes are always right and the design behind them is often not. The gap sits in what the arrows imply rather than in what the boxes say. So let us redraw it, and then walk through the six things the four box version quietly leaves out.

What the original gets right

Credit where it belongs. The separation of concerns in that infographic is sound, and plenty of teams still get it backwards.

API Management owns the contract. It publishes the API, applies policy, meters consumption, and gives consumers something stable to build against. Azure Functions owns the work. It runs your business logic, scales with demand, and bills for execution rather than for uptime.

That split matters because the alternative is worse. Teams that skip the gateway end up implementing authentication, throttling, and versioning inside every function, in slightly different ways, maintained by whoever touched it last. I wrote about a related boundary problem in Azure Functions, Logic Apps, and Power Automate: choosing the right tool, and the same principle applies here. The value of a gateway is not the features it lists. The value is the code it lets you delete.

So the boxes are right. Now for the arrows.

Gap one: a gateway is not a firewall

The original diagram lists “Security (OAuth, API Key)” inside the API Management box and leaves it there. That single bullet does a lot of quiet work, because it invites you to treat the gateway as your perimeter.

API Management applies policy. It validates tokens, enforces quotas, transforms payloads, and rejects malformed requests against a schema. It does not run an OWASP rule set, and it is not designed to absorb a volumetric attack aimed at your public hostname.

For anything internet facing, put Azure Front Door Premium or Application Gateway in front, with a WAF policy attached. Then lock the origin so the gateway only accepts traffic that arrived through the edge. Otherwise you have bought a policy engine and called it a perimeter.

This is also the cheapest gap to close, which makes it the most annoying one to find missing during a penetration test.

Gap two: subscription keys meter, tokens authenticate

Here is the conflation that causes the most damage in practice. A subscription key and an access token appear side by side in most diagrams, as if they were two ways of doing the same thing.

They are not. A subscription key identifies a product. It answers the question “which consumer agreement does this call belong to”, which is a billing and quota question. It does not tell you who the caller is, it travels in a header that ends up in scripts and log files, and it is shared across everyone using that product.

Authentication happens in the validate-jwt policy. That is where you check the signature, the audience, the issuer, and the claims that decide whether this particular caller may create an order.

<validate-jwt header-name="Authorization" failed-validation-httpcode="401">
<openid-config url="https://login.microsoftonline.com/{{entra-tenant-id}}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>{{orders-api-audience}}</audience>
</audiences>
<required-claims>
<claim name="roles" match="any">
<value>Orders.Write</value>
</claim>
</required-claims>
</validate-jwt>

Once that is in place, the rate limit should follow the same identity. Throttling by IP address punishes everyone behind a corporate NAT and protects you from nobody who has a laptop and patience. Throttling by a claim from the validated token gives you a quota per caller, which is what the consumer actually agreed to.

<rate-limit-by-key calls="60" renewal-period="60"
counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization","").AsJwt()?.Claims.GetValueOrDefault("oid", "anonymous"))" />

Gap three: the arrow nobody draws

Now for the one that matters most.

Every version of this diagram shows one arrow into the Function App. That arrow implies the gateway is the way in. It is not. It is a way in.

An HTTP triggered function sits on a public hostname by default. Anyone holding that hostname and a function key can call it directly, and that call skips the WAF, the token validation, the rate limit, the audit trail, and every policy you carefully wrote. The gateway becomes a convention rather than a control, and conventions do not survive contact with an incident.

Teams close this in three stages, usually in this order.

Layer one, the function key. Store the host key as a secret named value in API Management, backed by Key Vault, and let the backend inject it as x-functions-key. The key never appears in your policy file or your repository. This stops crawlers and casual discovery. It does not stop anyone who has ever seen the key, and keys have a way of ending up in Postman collections, log files, and support tickets.

Layer two, token validation on the function itself. Turn on the built-in authentication, point it at the app registration that represents your API, and an untokened call now fails at the platform before your code runs. Better. Still not closed, because a valid token replayed straight at the function hostname bypasses everything the gateway added on top.

Layer three, take it off the internet. Set publicNetworkAccess to Disabled, put a private endpoint in front of the app, and integrate the gateway into the same virtual network. The hostname stops resolving from outside. Now there is genuinely one route in.

resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
properties: {
publicNetworkAccess: networkIsolation ? 'Disabled' : 'Enabled'
virtualNetworkSubnetId: networkIsolation ? functionSubnetId : null
}
}

Layer three is the only one that turns your architecture diagram into a statement about reality. It also has a prerequisite that catches people out, which is the next gap.

Gap four: the tier decides the architecture

Private networking is not a checkbox you add at the end. It is gated by the API Management tier and by the Functions hosting plan, and those two choices constrain everything else in the design.

The v2 tiers changed the arithmetic here. Outbound virtual network integration used to mean Premium, which put private connectivity out of reach for a lot of internal APIs. Standard v2 brought it within reach of an ordinary project budget. On the compute side, the Flex Consumption plan brought virtual network integration to a consumption billing model, which used to mean choosing between cost and connectivity.

The same is true of time. Both the gateway and the function cap how long a request may run, and those caps differ by tier and plan. If your design assumes a two minute synchronous call, you have made a tier decision without realising it.

Pick the tier and the hosting plan before you pick the features, and verify the current limits on Microsoft Learn rather than trusting a diagram. Both sides of this moved several times during 2025 and 2026, and anything I write here has a shelf life.

Gap five: the dashed response arrow assumes synchronous

Look at the original infographic again. The response arrow is dashed and runs straight back to the client. It quietly assumes every call finishes inside the request.

Plenty do not. Bulk imports, report generation, anything that fans out to a slow downstream system. The instinct is to raise the timeout, first on the function, then on the gateway, until the whole chain waits for the slowest possible caller. That is not a fix. It is a queue with worse ergonomics, and it fails under load rather than under test.

The pattern that works is the asynchronous HTTP API. Accept the request, start the work, and answer immediately.

var instanceId = await durableClient.ScheduleNewOrchestrationInstanceAsync(
nameof(BulkImportOrchestrator), orders);
var response = request.CreateResponse(HttpStatusCode.Accepted);
response.Headers.Add("Location", $"{request.PublicBaseUrl()}/bulk/{instanceId}");
response.Headers.Add("Retry-After", "5");

One detail deserves attention, because it connects back to gap three. Durable Functions ships a helper called CreateCheckStatusResponse that builds the polling URLs for you. Those URLs point at the function hostname. That leaks your backend to every caller, and it breaks the moment you disable public network access.

So build the status URL from a setting that holds the gateway address instead. Callers should never learn the name of your function app, and they certainly should not be given it in a header.

Gap six: one request, two telemetry stores

Both boxes in the diagram write to Application Insights, usually to two separate resources with two separate sampling configurations. Neither picture shows that, because telemetry is drawn as a property rather than as a system.

The result shows up during your first real incident. You have a gateway trace that ends at the backend call and a function trace that starts somewhere in the middle, and no reliable way to join them. Then somebody discovers that the gateway sampled at one rate and the function at another, so half the pairs do not exist at all.

Two habits fix this. Set a correlation id at the gateway when the caller did not supply one, and propagate it through the function into every log line.

<set-variable name="correlationId"
value="@(context.Request.Headers.GetValueOrDefault("x-correlation-id", context.RequestId.ToString()))" />
<set-header name="x-correlation-id" exists-action="override">
<value>@((string)context.Variables["correlationId"])</value>
</set-header>

Then align the sampling on both sides, and write the query that joins them before you need it rather than during an outage. I made a similar argument about governed telemetry in the Foundry Citadel Platform series, where an SDK routed its calls around the gateway entirely and the only reason we noticed was that the traces did not add up.

Where this is the wrong answer

Everything above assumes you need API Management. Often you do not.

A gateway earns its place when there is a portfolio to govern. Several APIs, consumers outside your own team, versions that have to coexist, quota that differs per consumer, one place where policy and audit live. Under those conditions API Management pays for itself in the coordination it removes.

For a single internal API with one consumer, it is a monthly bill wrapped around a proxy. Built-in authentication with Entra ID on the function, a private endpoint if it is internal, and you are done. Add the gateway when the second consumer actually appears, because that is when versioning and per consumer quota start to matter.

There is also a middle answer that gets skipped. One public API that needs a WAF but has no consumer lifecycle should sit behind Front Door and nothing else. You get the perimeter without buying governance you are not using yet.

A sample you can deploy

I put the whole thing in a repository, because policy fragments in a blog post are easy to agree with and harder to run.

One azd up deploys API Management Standard v2, a Flex Consumption function app on .NET 8 isolated, a storage account with shared key access disabled, and a shared Application Insights resource. The Orders API has a synchronous endpoint, an asynchronous bulk import built on Durable Functions, and a health endpoint the gateway can probe.

The three layers from gap three are deployment switches rather than prose:

SettingWhat it turns on
defaultLayer one, function key held as a secret named value
ENTRA_TENANT_ID and API_AUDIENCELayer two, validate-jwt and quota by oid claim
NETWORK_ISOLATION=trueLayer three, private endpoint and public access disabled

Deploy it with the default settings, then call the function hostname directly and watch it answer. That single curl makes the argument better than this whole post does.

Closing

The four box diagram is a good summary and a bad specification. Function App handles the code, API Management handles the contract, and neither of them handles the network. That last part is where these designs usually fail, and it is the part no infographic ever draws.

Azure Logic Apps Agentic Workflow Security in Production

Part 6 of 7 in the Logic Apps Agent Loop series

Part 5 covered multi-agent patterns in the Azure Logic Apps agentic workflow series. Each pattern extends your agent’s reach, but that reach comes with a security cost. The more capable and connected your agent, the more important it is to understand who can call it and under what conditions. This post covers the expanded caller surface, the developer key’s limitations, and the full production security stack.

Conventional Logic Apps workflows have a bounded caller surface. The callers are known systems: a scheduler, a service bus, and an HTTP client you control. The authentication model is straightforward: SAS tokens, Managed Identity, and IP filtering. Agentic workflows fundamentally change this, particularly conversational ones. When you expose a chat interface to external callers, those callers can be people, other agents, MCP servers, or automation clients from networks you do not control. The security model has to change with the threat model.

The expanded caller surface

The shift from nonagentic to agentic workflows introduces a qualitatively different caller population. In a nonagentic workflow the trigger is called by a known system at a known time for a known reason. In a conversational agentic workflow the trigger is called by:

  • Human users interacting through an external chat client
  • External agents invoking the workflow as a tool
  • MCP servers routing requests through the workflow
  • Automation clients from untrusted or unknown networks

Each of these caller types introduces different identity, trust, and access control requirements. A billing system calling a webhook is easy to reason about. An external agent calling your workflow from an unknown network at unpredictable intervals is not.

This expanded surface area is why Microsoft’s documentation draws a sharp distinction between the developer key used during design and testing in the Azure portal and proper production authentication. Understanding that distinction is the starting point for securing any agentic workflow.

The developer key: what it is and what it is not

Understanding the developer key’s limitations is the starting point for any serious Azure Logic Apps agentic workflow security implementation. When you test a conversational agentic workflow in the Logic Apps designer, the Azure portal authenticates your test calls using a developer key. The developer key is a convenience mechanism that lets you skip manual authentication setup during development. It fires automatically when you run a workflow, call a Request trigger, or interact with the integrated chat interface.

The developer key has five hard limitations that make it unsuitable for production:

  • It is not a substitute for Easy Auth, Managed Identity, federated credentials, or signed SAS callback URLs.
  • In addition, it is designed for large or untrusted caller populations, agent tools, or automation clients.
  • It is also not a per-user authorization mechanism; it has no granular scopes or roles.
  • And finally, it is not governed by Conditional Access policies at the request execution layer, only at the portal sign-in layer. And it is not intended for programmatic or CI/CD usage.

The developer key is linked to a specific user and tenant based on an Azure Resource Manager bearer token. Because of that binding, you cannot distribute it externally. It is, in the Microsoft documentation’s own framing, a mechanism for quick testing before you formalize authentication, not a path to production.

Azure Logic Apps agentic workflow security: Standard versus Consumption

The right production authentication mechanism depends on your Logic Apps hosting model.

Setting up Managed Identity for backend connections

Easy Auth secures who can call your agentic workflow. Managed Identity secures what your workflow can call. These are two distinct security concerns and both need to be addressed in production.

When your agent invokes a tool, Azure OpenAI, Azure AI Search, a storage account, or a Service Bus namespace, that call needs to be authenticated. The default approach during development is often to store an API key or connection string in app settings. In production, replace these with Managed Identity connections wherever possible. This removes credentials from app settings entirely. The logic app authenticates to backend services using its Azure AD identity, which is governed by RBAC, auditable, and revocable without rotating keys.

  1. Go to your la-agent-loop resource → Identity → System assigned → turn Status to On
  2. Save — Azure assigns a service principal to the logic app
  3. In each target resource (Azure OpenAI, AI Search, Storage), go to Access control (IAM) → Add role assignment
  4. Assign the appropriate role to the logic app’s Managed Identity:
    • Azure OpenAI: Cognitive Services OpenAI User
    • Azure AI Search: Search Index Data Reader
    • Azure Blob Storage: Storage Blob Data Reader
  5. In the Logic Apps connections, switch from API key authentication to Managed Identity for each backend service where possible.

Note: Managed Identity authentication for the agent model connection is only supported when the model type is AzureOpenAI. If your workflows use the MicrosoftFoundry model type, as in this series, the agent connection must use Key authentication. Managed Identity remains the right choice for all other backend connections such as Azure AI Search, Blob Storage, and Service Bus.

Setting up Easy Auth for your Azure Logic Apps agentic workflow

For Standard logic apps, the production authentication path is Easy Auth, also known as App Service Authentication. Easy Auth is an App Service platform feature that sits in front of your logic app and enforces identity-based authentication on every incoming request before it reaches your workflow.

When you enable Easy Auth on a Standard logic app, external callers, whether human users, external agents, or MCP servers, must present a valid identity token. Easy Auth validates the token against Microsoft Entra ID before allowing the request through. This gives you full Conditional Access policy enforcement, per-user identity, token revocation, and audit logging, the full production security stack.

To set up Easy Auth on a Standard logic app:

  1. In the Azure portal, open your la-agent-loop logic app resource
  2. Navigate to Authentication in the left sidebar under Settings
  3. Click Add identity provider
  4. Select Microsoft as the identity provider
  5. Under App registration, select an existing registration or choose Create new app registration and name it la-agent-loop-auth
  6. Under Supported account types, select Current tenant — single tenant for internal workloads
  7. Set Unauthenticated requests to HTTP 401 Unauthorized: recommended for APIs
  8. Leave Token store enabled
  9. Click Add

Note: Easy Auth operates at the App Service host level, before the Logic Apps runtime processes the request. Authentication failures are rejected at the infrastructure layer with a 401 the workflow never executes and no run history entry is created for unauthenticated calls.

Consumption: OAuth 2.0 with Microsoft Entra ID

For Consumption logic apps, configure an agent authorization policy on the logic app resource using OAuth 2.0 with Microsoft Entra ID. This provides equivalent identity enforcement to Easy Auth for the Consumption hosting model. For the full configuration steps, see Create conversational agent workflows in Azure Logic Apps on Microsoft Learn.

Key Vault for secrets that cannot use Managed Identity

Not every connection in an Azure Logic Apps agentic workflow supports Managed Identity. Where API keys or connection strings are unavoidable, store them in Azure Key Vault and reference them from Logic Apps app settings using the Key Vault reference syntax:

@Microsoft.KeyVault(SecretUri=https://your-keyvault.vault.azure.net/secrets/your-secret/)

This keeps credentials out of app settings in plain text, provides centralized rotation, and gives you audit logs of every secret access. The Standard logic app accesses Key Vault using its Managed Identity; no separate credentials are needed for the vault itself.

Network controls for Standard workflows

Standard logic apps run on the App Service infrastructure, which gives you network-level controls that Consumption workflows do not have:

Private endpoints allow your logic app to receive inbound traffic only from within a virtual network, removing public internet exposure entirely. This is the recommended configuration for production agentic workflows that serve internal users or agents.

VNet integration allows your logic app to make outbound calls to services within a virtual network, including on-premises systems, private Azure services, and internal APIs, without exposing those services to the internet.

IP access restrictions let you restrict inbound traffic to specific IP ranges at the App Service level, providing a lighter-weight alternative to private endpoints for scenarios where full network isolation is not required.

For production agentic workflows processing sensitive data, patient records, financial data, internal business intelligence, and private endpoints with VNet integration is the right starting point.

Azure Logic Apps agentic workflow security checklist

Before going live with any agentic workflow:

  • Easy Auth configured with Microsoft Entra ID (Standard) or OAuth 2.0 agent authorisation policy (Consumption)
  • Developer key not used or referenced in any production caller
  • Managed Identity enabled on the logic app and assigned to all backend services
  • API keys and connection strings moved to Key Vault references
  • Private endpoints configured for Standard workflows handling sensitive data
  • Conditional Access policies applied to the Entra ID app registration backing Easy Auth
  • Run history access restricted to authorised operations personnel

What comes next

The final post in this series concludes with operations: Application Insights integration, agent loop pricing, run history analysis, and deployment of agentic workflows through a CI/CD pipeline. Part 7 covers everything you need to run agent loops confidently in production.