It's easy to publish Foundry Agents and your own Custom Engine Agents, so they can be accessed in Teams and Copilot. But if you want to run your agents on your own private network you'll hit an issue. Teams and Copilot live outside your network but your agent lives inside. How do we connect them?
Asking your network security team to setup public access to your agent is unlikely to end well! By the end of the article, I hope to have given you a solid understanding of the flows and security involved in pro-code Azure Agents. Hopefully you'll have a pathway to safely connect your agent to Teams and Copilot.
Published Agents and Azure Bots
When you publish an Agent in Microsoft Foundry, itâs worth understanding whatâs happening under the hood. Itâs more familiar than you might think. Behind the scenes, Foundry creates an Azure Bot resource on your behalf. The same Azure Bot resource thatâs been around for years, working exactly as it always has.
Every Azure Bot needs a messaging endpoint. It's a URL that Microsoft's Channel Adapters (Teams, Copilot, and others) will POST incoming messages to. Foundry hosts and manages that messaging endpoint for you. It speaks the Activities protocol, a standard protocol used across Microsoftâs channels including Teams and Copilot. If youâre in Foundry you donât need to write or host this endpoint yourself; Foundry takes care of it on your behalf. If youâre building a custom Agent, youâll need a host for it.
The Channel Adapters live outside your network - in Microsoftâs infrastructure. And this is where things get interesting - because in our scenario, the Agent is deployed with a Private Endpoint. The messaging endpoint Foundry is hosting for us is only reachable from inside our network. The Bot Channel Adapter, sitting outside, has no way in.
Figure 1: The Microsoft Channel Adapters cannot reach your Agent when itâs behind a firewall.
We have two challenges to address, and Iâm purposefully making this as strict as possible security-wise:
- How do we get traffic to our Agent?
- What controls can we apply to that traffic to work within the confines of a classical security-zoned network?
Getting Traffic to the Agent
You can find the messaging endpoint by looking at the Azure Bot Resource that Foundry has created in the Azure Portal. It will point to the Activity Protocol URL that Foundry is hosting on your behalf. This is what the Microsoft Channel Adapter needs to reach.
If you are using a A365 SDK Agent, this is done a bit differently. Look at your a365 config and youâll see a messaging endpoint URL. You can use the a365 cli to change it.
The catch: because weâve deployed our A365 Agent or Microsoft Foundry with a Private Endpoint, that URL resolves to a private IP address when inside our network. From the outside â itâs a Public IP address which we cannot access. Dig the hostname and youâll see the CNAME:
From inside the network the privatelink hostname resolves correctly and everything works. From outside - where the Microsoft Channel Adapters lives - try to hit it and youâll get a HTTP/1.1 403 Forbidden response. The endpoint isnât accessible from the Public Internet. We need a pathway in via our firewall.
Opening a Path Through the Firewall
All firewalls are different, and your organisationâs setup will shape your approach. Iâm using a Basic Azure Firewall - layer 4, no TLS termination.
The first thing to do is setup DNS so we can reach the firewall from the Public Internet. But we hit our first challenge here. We canât add an A record to the Microsoft owned <resource>.services.ai.azure.com. Instead we need to use our own DNS to use a hostname that we own and add an âAâ record pointing to our firewallâs Public Ip address.
For this example, Iâm using teamsagent.experiments.graemefoster.net.
So letâs have a first attempt â we can create a DNAT rule in my firewall that forwards anything arriving at the firewall on port 443 to the Foundry Agentâs Private Endpoint IP on port 443. In the real world. As youâll quickly discover this wonât work, but itâs the first little step along the way of getting traffic to Foundry.
The TLS Trust Problem
With the DNAT rule in place we have a pathway in, but the Channel Adapter also needs to be sure itâs talking to the right endpoint. HTTPS trust is based on the FQDN matching the TLS certificate presented by the target service.
Weâve broken that. Traffic is arriving at teamsagent.experiments.graemefoster.net, but the Foundry Agent returns a Microsoft certificate valid for *.cognitive.microsoft.com. The Channel Adapter will refuse the connection for good reason. The certificate presented by the endpoint the Channel Adapter has connected to is not what it expected to see.
Terminating TLS
To fix this we need something in front of the Foundry Agent that presents a TLS certificate valid for our own domain - teamsagent.experiments.graemefoster.net. Foundry canât do this - the messaging endpoint always lives on a Microsoft-controlled FQDN.
|
â ď¸ Why not Azure Application Gateway?
App Gateway is often the first suggestion for TLS termination in front of a private endpoint - but it doesnât help here. It has no ability to inspect or validate JWT tokens. So even with App Gateway handling TLS, youâd still need something behind it (APIM or a YARP proxy) to do JWT validation. That makes it a redundant extra hop. We also donât own the code running on the Agent, so we canât push validation there.
App Gateway isnât a good choice for this use case.
|
Instead, Iâm going to use Azure API Management (APIM). It sits in our private network, terminates TLS with our own certificate, and can validate JWTs natively in its inbound policy - no custom code required. If youâd prefer a code-based approach, a YARP reverse proxy on Azure App Service or Azure Container Apps works equally well. You will need a TLS certificate for your custom agent host. Azure App Service makes it easy to get a TLS certificate if you can prove domain ownership.
With APIM in place, the traffic flow looks like this:
Figure 2: APIM terminates TLS and validates the JWT before forwarding to the Foundry Agent on the private network.
We now have a trustworthy end-to-end pathway. Letâs look at the YARP option briefly before getting into what weâre actually validating.
Alternative: YARP on App Service / ACA
If youâd prefer a code-based approach, YARP (Yet Another Reverse Proxy) deployed to Azure App Service or Azure Container Apps does the same job. You add a route to forward traffic to the Foundry Agentâs private endpoint and write ASP.NET middleware to validate the Microsoft Bot issued JWT before forwarding.
APIM is a managed service so no code to maintain - just a bit of XML policy. YARP keeps everything in your own codebase with full control over the validation logic. Either is a solid choice. Pick whichever fits your teamâs operational model.
Knowing Whoâs Knocking - Security Control Points
At this point a well-run ARB will ask: how do we know a request actually came from Microsoftâs Bot Service, and not from someone who discovered our firewall rule?
The answer lives in the JWT (JSON Web Token) that the Bot Channel Adapter attaches to every request. There are two control points your ARB will care about:
- Is this token genuinely from Microsoftâs Bot Idp? (JWT signature validation)
- Is it for our specific Agent and not just any bot? (Bot Client ID / audience validation)
The Bot Service JWT
Every time the Bot Channel Adapter sends a message to the messaging endpoint, it includes a signed JWT in the Authorization header as a Bearer token. This token is issued by the Microsoft Bot Idp and signed with Microsoftâs private key.
You verify the signature using Microsoftâs public keys via the Microsoft Bot Idpâs OpenID Connect metadata. The full authentication spec is documented at https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication
The key claims unique to Bots to validate in every incoming JWT are:
- iss (Issuer) must be https://api.botframework.com
- aud (Audience) must match your Botâs Microsoft App ID (more on this below)
The Bot Client ID (Microsoft App ID)
Each Azure Bot has a Microsoft Entra ID App Registration associated with it. This is what gives Agent a unique identity. When Foundry creates the Azure Bot for your published Agent, it Agent Blueprint and an Agent Identity. The Application (Client) ID of the Agent Identity is set as the Botâs Client ID
This Client ID uniquely identifies your specific Agent across all of Microsoftâs infrastructure. All calls to your Agent from Microsoftâs Bot Channel Adapters will have an âaudâ claim equal your Bot Client ID. This means::
- If an attacker intercepts and replays a JWT, theyâre replaying it at your bot. APIM will still validate the signature and expiry. An expired token gets a 401.
- If a different Agent tries copies your endpoint, the JWT carry a different Agentâs App ID in the aud claim. APIM rejects it immediately
A token from the Bot Channel to the /messages endpoint
|
Where to find your Bot Client ID:
Azure Portal â Your Azure Bot resource â Configuration â Microsoft App ID Or for an Custom Engine Agent, "a365 config display -g" will give you the 'agentBlueprintId' which is its App Id
|
Enforcing Controls at the Perimeter with APIM Policy
You can enforce these checks at APIM, before the request ever reaches the Foundry Agent. APIMâs validate-jwt inbound policy handles this natively:
<inbound>
<validate-jwt header-name="Authorization" require-scheme="Bearer">
<openid-config url="https://login.botframework.com/v1/.well-known/openidconfiguration" />
<audiences>
<audience>YOUR-BOT-CLIENT-ID</audience>
</audiences>
<issuers>
<issuer>https://api.botframework.com</issuer>
</issuers>
</validate-jwt>
<base />
</inbound>
Replace YOUR-BOT-CLIENT-ID with the Microsoft App ID from your Azure Bot resource. With this policy in place, APIM will:
- Fetch and cache the Microsoft Bot Idpâs signing keys automatically via the OpenID Connect metadata URL.
- Cryptographically verify the JWT signature on every request.
- Reject any request whose aud doesnât match your Bot Client ID, returning a 401 before the request reaches the Agent.
- Reject expired, not-yet-valid, or malformed tokens.
Wait. What About the Reply? (Custom Engine Agents only)
So far weâve thought entirely about inbound traffic like messages arriving from Teams or Copilot into our Agent. But a conversation goes both ways. When a Custom Engine Agent has something to say back, it doesnât reply on the incoming HTTP connection. Instead, it makes a separate outbound POST to the Microsoft Bot channel adapters, which deliver the message back to the channel the user is on.
The URL it calls may be in the original JWT. Itâs probably something like:
https://smba.trafficmanager.net/amer/
The important point is that the two paths are entirely independent connections. The Agent receives on one and initiates the reply on another. Both need to work.
Configuring the Outbound Firewall
With all outbound traffic going through a corporate firewall youâll need outbound to these endpoints:
smba.trafficmanager.net Bot reply endpoint
login.microsoftonline.com Entra ID token acquisition
login.botframework.com Bot needs to fetch our Idpâs OIDC metadata
|
|
â ď¸ The silent failure mode.
The inbound failure is obvious: the Bot Channel Adapter gets a connection error straight away and you never see the request.
The outbound failure is subtle: messages arrive, the Agent processes them and posts a reply. But the call to smba.trafficmanager.net is blocked by the proxy and times out silently. The user just never sees a response.
If your Agent receives messages but never replies check in firewall logs for dropped outbound connections.
|
Talking to Your Firewall Team
Letâs bring it all together. Hereâs the full picture for the network and security teams that need to approve this.
What Youâre Asking For (Inbound)
Source: * (or lock down to AzureBotService / regional versions in Azure IP addresses) Destination: Your Firewall Public IP Translated Address or FQDN: Api Management URL / Yarp Proxy App Service URL Translated Port: 443 Protocol: TCP
|
Microsoft publishes Bot IP ranges under the AzureBotService service tag in the Azure IP Ranges and Service Tags file:
https://www.microsoft.com/en-us/download/details.aspx?id=56519
Restricting source IPs to these ranges adds a network-layer control on top of JWT validation. It's defence in depth at two independent layers.
What Youâre Not Asking For
A common concern is that opening any inbound port creates risk. Hereâs what helps address that:
- No inbound traffic ever reaches the Foundry Agent directly - APIM validates every request before forwarding.
- Unauthenticated or incorrectly-targeted requests are dropped with a 401 at the perimeter.
- The Foundry / A365 Agent has no public IP and is only reachable on the private network.
- Outbound from the Agent only goes to named, Microsoft-published endpoints. Not arbitrary internet destinations.
The Full network security story
- Network perimeter (inbound). Restricted to AzureBotService IP ranges only.
- TLS is terminated at APIM / YARP! with your own certificate;
- Authentication - every inbound request carries a Microsoft-signed JWT, validated cryptographically at APIM.
- Authorisation - JWT audience validated against your specific Bot Client ID; any other botâs request is rejected.
- No direct Agent exposure - the Foundry Agent Private Endpoint is unreachable from the public internet.
- Outbound â to specific FQDNâs which can be filtered using Application rules (which look at SNI headers) on outgoing request.
Wrapping Up
Publishing a Foundry / Custom Engine Agent for use in Teams and Copilot inside a regulated, firewall-controlled environment is absolutely achievable. It just requires understanding all the layers involved, including the ones people don't think about until something silently fails.
The Azure Bot plumbing that Foundry creates under the hood is the same plumbing that's been running production workloads for years. The JWT the Bot Channel Adapter sends with every request gives you strong, cryptographically verifiable proof of identity. APIM (or YARP) gives you a clean enforcement point at the perimeter, well away from the Agent itself.
If this pattern fits a challenge you're working through, I'd love to hear how it lands in your environment - drop a comment below or reach out directly. And if your organisation is at the stage of designing the broader Agent platform, thinking about how identity, routing, and security controls fit together across multiple Agents, that's a conversation worth having before the architecture gets locked in. The security work up front is what lets you move fast when the business wants more Agents in a hurry.
Happy building!
Graeme