Securing Your Pro-Code Custom Engine Agent for Microsoft 365 Copilot: Endpoint Protection Guide
daisami presents a practical guide on securing pro-code custom engine agent endpoints for Microsoft 365 Copilot, detailing application-level controls, code samples, and Azure integration strategies.
Securing Pro-Code Custom Engine Agents for Microsoft 365 Copilot
Author: daisami
Last updated: Aug 08, 2025, Version 5.0
Introduction
If you’ve built a custom engine agent for Microsoft 365 Copilot using pro-code approaches like C#, this guide will help you secure its endpoints—especially as your solution becomes publicly accessible via Azure App Service and integrates with Microsoft Teams through Azure Bot Service.
Key Endpoints That Require Security
The architecture exposes three main endpoints:
- Teams Endpoint (user entry via Microsoft Teams)
- Azure Bot Service Endpoint (acts as a relay between Teams and bot backend)
- ASP.NET Core Endpoint (your main app, likely hosted on Azure App Service in production)
Each requires different layers of protection.
1. Teams Endpoint Control
Control is established using Teams app management in your Microsoft 365 tenant. Admins upload your custom agent manifest and restrict access through Teams Admin Center at the user or group level. Limitations: You can’t restrict access at the endpoint level or prevent external orgs from copying the manifest. Thus, endpoint-level network protection isn’t feasible here.
2. Azure Bot Service Endpoint
This relay is managed by Azure and allows little configuration beyond specifying the Service Principal. The main goal is to ensure the connected backend endpoint is secured.
3. ASP.NET Core Endpoint (Critical!)
When exposing your custom engine agent (the bot backend), you often host it on Azure App Service, making it accessible from the public internet. Note: Microsoft network isolation is not supported for Teams channel bots, so you must use app-level controls (token validation, IP filtering, mTLS, etc.).
Security Implementation: Application-Level Controls
Enforcing JWT Token Validation in ASP.NET Core
In your Program.cs
, ensure you register authentication and token validation services:
builder.Services.AddBotAspNetAuthentication(builder.Configuration);
The AddBotAspNetAuthentication
extension is usually defined in your project (e.g., AspNetExtensions.cs
). This method reads issuer/audience settings from appsettings.{env}.json
and configures JWT validation accordingly. The snippet below highlights key points:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
ValidIssuers = validTokenIssuers,
ValidAudiences = audiences,
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
};
// ...
});
- Ensure
aud
(audience) claim matches your Service Principal (bot registration) - Handle different OpenID metadata URLs based on Azure/Gov cloud
Critical Security Fixes:
- Reject requests with missing/empty token: Update
OnMessageReceived
so any request without an Authorization header is explicitly failed (context.Fail(“Authorization header is missing.”)) - Restrict access to authorized Entra ID tenants: Load expected TenantId from configuration and compare at runtime. Reject requests originating from unknown or mismatched Tenant IDs.
Example: Validating and Rejecting Unauthorized Requests
In your middleware or bot handler (MessageActivityAsync
):
if (activity.ChannelId != "msteams" ||
activity.Conversation?.ConversationType?.ToLowerInvariant() != "personal" ||
string.IsNullOrEmpty(activity.From?.AadObjectId) ||
(!string.IsNullOrEmpty(_tenantId) && !string.Equals(activity.Conversation?.TenantId, _tenantId, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogWarning($"Unauthorized serviceUrl detected: {activity?.ServiceUrl}. Expected TenantId: {_tenantId}");
await turnContext.SendActivityAsync("Unauthorized service URL.");
return;
}
Service Principal Permissions
Ensure your Service Principal (App Registration) includes User.Read.All
and other necessary API permissions, or token issuance/validation will fail.
Additional Resources
- Azure Bot Service Teams Integration
- Network Isolation Limitations for Teams Channel
- Practical Bot Service Security Sample (GitHub)
Bottom line: For Microsoft Teams integrated bots, network isolation isn’t feasible; focus on hardened authentication, authorization, and robust token validation logic at the ASP.NET Core endpoint to minimize risk.
This post appeared first on “Microsoft Tech Community”. Read the entire article here