26 de maio de 202624 min de leitura

Como expor seus agentes de IA no Microsoft 365 Copilot sem reconstruir tudo

JamesN

Azure

Banner - Como expor seus agentes de IA no Microsoft 365 Copilot sem reconstruir tudo

Como expor seus agentes de IA no Microsoft 365 Copilot sem reconstruir tudo

TL;DR: Se você tem um serviço agentic funcional (LangChain, Semantic Kernel, etc.) e quer expô-lo no Microsoft 365 Copilot, não precisa reconstruí-lo como um agente declarativo. Coloque um M365 Gateway stateless na frente — ele lida com o protocolo Bot Framework, valida tokens de canal e executa a primeira troca OBO. Seu serviço original permanece intacto, com sua própria lógica de orquestração, sessão e chamadas downstream delegadas. A conclusão principal: você ganha integração com o Copilot sem perder o controle sobre sua arquitetura.

Você construiu um serviço agentic. Ele funciona. Usa o framework que você escolheu — talvez LangChain, Semantic Kernel, Microsoft Agent Framework, ou algo totalmente próprio. Ele conversa com seu LLM, chama suas APIs downstream e gerencia conversas multi-turn do jeito que você projetou.

Agora alguém pergunta: "Dá pra colocar isso no Microsoft 365 Copilot?"

O caminho óbvio é reconstruir o agente usando M365 Copilot Agents (declarative ou custom engine). Mas isso significa abrir mão do controle — sobre sua lógica de orquestração, suas escolhas de framework, seu gerenciamento de sessão e a forma como você chama serviços downstream em nome do usuário logado.

Este guia é para o outro caminho. Aquele onde você mantém seu serviço agentic existente praticamente intacto e coloca um M365 Gateway na frente dele — um serviço stateless que lida com o protocolo Bot Framework, valida tokens de canal, realiza a primeira troca de token On-Behalf-Of (OBO) e traduz as conversas do Copilot para a API nativa do seu serviço. Seu serviço pode continuar agnóstico em relação a Bot e Teams, mas se ele possui acesso delegado a downstreams, deve validar o token de serviço recebido em sua própria fronteira e usar essa validação para sua própria cadeia OBO.

Quando usar este padrão?

  • Você tem (ou quer construir) sua própria implementação agentic na tecnologia e framework de sua preferência
  • M365 Copilot Agents — declarative ou custom engine — não te dão controle suficiente sobre orquestração, tool calling ou auth downstream
  • Você quer propriedade total da lógica agentic: pro-code, seu LLM, seus prompts, suas ferramentas, seu session store
  • Você precisa de acesso delegado a serviços downstream (bancos de dados, APIs, servidores MCP) via fluxos OBO encadeados, e quer ser dono da cadeia de tokens de ponta a ponta

O que segue é uma diretriz geral de desenvolvimento — framework-agnostic, linguagem-agnostic — para deploy de qualquer serviço agentic atrás do Azure Container Apps, expondo-o ao M365 Copilot através de um M365 Gateway e usando fluxos OBO encadeados para chamar serviços downstream como o usuário logado.

Qual a arquitetura?

A arquitetura separa responsabilidades em dois serviços independentemente implantáveis:

Diagrama de Arquitetura

Camada Responsabilidade Estado
M365 Copilot Identidade do usuário, SSO, UX de conversa Nenhum (plataforma)
Gateway Adaptador de protocolo Bot, auth de canal, troca OBO #1 Stateless
Serviço Agentic Lógica de negócio, validação de token, OBO downstream, memória de sessão Stateful
Downstream Dados, APIs, servidores MCP — chamados como o usuário delegado Externo

Por que dois serviços?

  • Separação de trust boundaries — O gateway lida com protocolo Bot Framework e auth de canal. O serviço é dono do acesso a dados/negócio, valida o bearer que recebe e nunca precisa de credenciais de Bot.
  • Escalabilidade independente — O gateway pode escalar para muitas réplicas. O serviço (com sessões em memória) roda como réplica única para MVP.
  • Liberdade de framework — O serviço pode usar qualquer framework agentic, provedor de LLM ou lógica customizada. O gateway é apenas um forwarder HTTP.
  • Adapte sem modificar — Se você já tem um serviço agentic, pode levá-lo ao M365 Copilot escrevendo apenas o gateway + um adapter de cliente de serviço. O serviço existente fica intocado.

Como funciona o fluxo de tokens?

Fluxo de Tokens OBO

A identidade do usuário flui de ponta a ponta através de duas trocas OBO encadeadas. Nenhum serviço armazena ou armazena em cache senhas de usuário.

Princípios chave

  1. O token do usuário nunca sai da cadeia OBO. Cada serviço recebe uma assertion escopada e a troca para o próximo hop. Nenhum serviço vê a senha original.
  2. A validação JWT é inegociável — e dividida por fronteira. O gateway deve validar o token de canal que recebe do Microsoft 365. O serviço deve validar o bearer scoped antes de usá-lo para OBO, ownership de sessão ou scoping de usuário.
  3. Ingress interno para o serviço é hardening recomendado, não a única opção segura. No ACA, private ingress é um padrão forte. Se o serviço permanecer acessível externamente, ele deve forçar sua própria validação de bearer e checagens de autorização exatamente da mesma forma.
  4. ContextVar carrega a assertion validada. O serviço vincula o JWT validado a uma ContextVar async-safe no middleware, para que qualquer chamada OBO downstream dentro da mesma requisição o pegue automaticamente.

Quais registros de app no Entra ID são necessários?

Você precisa de dois registros de app no Entra ID, além de saber o resource app ID da sua API downstream.

App Registrations

Passo a passo do registro

1. App do Serviço Agentic

Configuração Valor
Display name your-service-api
Identifier URI api://<service-client-id>
Sign-in audience AzureADMyOrg (single tenant)
requestedAccessTokenVersion 2
Exposed scope access_as_user (delegated, User consent)
Required permissions Downstream API user_impersonation (delegated)
Client secret Sim — necessário para MSAL ConfidentialClient OBO

2. App do Gateway / Bot

Configuração Valor
Display name your-bot
Identifier URI api://botid-<bot-client-id> (convenção Bot SSO)
Sign-in audience AzureADMyOrg
requestedAccessTokenVersion 2
Exposed scope access_as_user (delegated)
Required permissions Service app access_as_user (delegated)
Redirect URI https://token.botframework.com/.auth/web/redirect
Preauthorized clients Teams Desktop (1fec8e78-...) e Teams Web (5e3ce6c0-...)
Client secret Sim — necessário para Bot Framework auth

3. Consentimento do admin (obrigatório)

Ambas as permissões delegadas exigem consentimento do admin do tenant:

Gateway/Bot app  → Service app access_as_user       ← consentimento admin
Service app      → Downstream user_impersonation    ← consentimento admin

Sem isso, chamadas OBO retornam AADSTS65001: The user or administrator has not consented to use the application.

4. Conexão OAuth do Azure Bot

Crie uma conexão OAuth no recurso Azure Bot para que o Microsoft Agents SDK possa fazer troca de token silenciosa para o hop gateway-para-serviço:

az bot authsetting create \
  -g $RESOURCE_GROUP \
  -n $BOT_RESOURCE_NAME \
  -c SERVICE_CONNECTION \
  --service Aadv2 \
  --client-id $BOT_APP_ID \
  --client-secret $BOT_APP_PASSWORD \
  --provider-scope-string "$SERVICE_API_SCOPE offline_access openid profile" \
  --parameters TenantId="$TENANT_ID" TokenExchangeUrl="api://botid-$BOT_APP_ID"

Guia de implementação dos componentes

Componente 1: O M365 Gateway (Stateless, Adaptador de Protocolo)

O trabalho do gateway é ponte entre o protocolo Bot Framework e o contrato HTTP que seu serviço agentic expõe. Ele recebe atividades do Bot, valida o token de canal, troca o token SSO do usuário por um token scoped para o serviço (OBO #1) e traduz a chamada para a forma nativa da sua API.

Insight chave: Se você já tem um serviço agentic stateful — mesmo que não tenha sido projetado para M365 — o gateway pode se adaptar a ele. Você não precisa modificar seu serviço existente para conformar-se a um contrato de API específico. O gateway absorve a tradução do protocolo M365. O serviço ainda deve validar o bearer scoped que chega à sua própria fronteira de API.

Gateway Flow

Responsabilidades de validação de token no Gateway

Isso é um requisito duro, não opcional. O gateway é a fronteira de confiança pública para a requisição do canal. Se a validação do token de canal for pulada ou incompleta, callers não autenticados podem alcançar seu caminho de forwarding.

O gateway deve validar o token de canal de acordo com os requisitos do SDK/Bot Framework para o canal que atende. Após o OBO #1 produzir um bearer scoped para o serviço, o gateway pode inspecionar esse token para diagnósticos, roteamento ou forwarding opcional de metadados, mas o serviço deve permanecer o validador autoritativo para aquele token de serviço.

Bom comportamento do gateway:

  1. Validar tráfego de canal de entrada usando o middleware Bot/Agents SDK ou verificações de issuer/signature/audience equivalentes
  2. Adquirir o token scoped para o serviço com OBO #1
  3. Forward o bearer do serviço como está em Authorization: Bearer ...
  4. Opcionalmente extrair claims para logging ou headers de conveniência: oid (user object ID), tid (tenant ID), upn ou preferred_username
  5. Tratar headers forwardados como metadados não autoritativos. Eles ajudam com telemetria, correlação ou debug, mas o serviço deve derivar identidade do bearer validado que recebe.

Adaptando ao contrato de API do seu serviço

O gateway contém um service client — uma pequena classe adaptadora HTTP que traduz entre o modelo de conversa do Copilot e a API nativa do seu serviço. É aqui que você mapeia:

Conceito do Copilot Equivalente no seu serviço Mapeado no service client
conversation.id Session/thread/chat ID Mapear na criação ou primeira mensagem
Texto da mensagem do usuário Campo do body da requisição (pode ser text, message, prompt, input, etc.) Remodelar o payload da requisição
Texto de resposta Campo da resposta (pode ser reply, response, output, content, etc.) Extrair do payload de resposta
Autenticação Bearer token, API key ou header customizado Anexar o token delegado scoped no header/campo correto

Exemplo: adaptando a diferentes formas de serviço

# Service client para um serviço com POST /chat/{thread_id} 
class MyServiceClient:
    async def send_turn(self, session_id: str, text: str, token: str) -> str:
        resp = await self.http.post(
            f"{self.base_url}/chat/{session_id}",
            json={"prompt": text, "stream": False},
            headers={"Authorization": f"Bearer {token}"},
        )
        return resp.json()["response"]  # extrai da forma de resposta do serviço

# Service client para um serviço com POST /v1/conversations/{id}/messages
class AnotherServiceClient:
    async def send_turn(self, session_id: str, text: str, token: str) -> str:
        resp = await self.http.post(
            f"{self.base_url}/v1/conversations/{session_id}/messages",
            json={"content": text, "role": "user"},
            headers={"X-Api-Token": token},
        )
        return resp.json()["choices"][0]["message"]["content"]

O message handler do gateway permanece o mesmo independentemente da forma do service client:

async def handle_message(context):
    token = await agent_auth.get_token(context)
    session_id = context.activity.conversation.id
    reply = await service_client.send_turn(session_id, context.activity.text, token)
    await context.send_activity(reply)

O que o gateway precisa de qualquer serviço

O gateway só precisa que o serviço suporte três capacidades — em qualquer forma de API:

  1. Identidade de sessão/thread — Alguma forma de manter estado de conversa entre turnos (session ID, thread ID, conversation ID, etc.)
  2. Troca de mensagens — Um endpoint que aceita texto do usuário e retorna uma resposta
  3. Autenticação — Aceita um bearer token ou outra credencial que o gateway possa fornecer via OBO #1

O serviço não precisa usar um padrão de URL, schema de requisição/resposta ou framework específico. A classe service client do gateway é o único lugar onde você codifica esses mapeamentos. O serviço ainda deve validar o bearer que recebe antes de confiar em qualquer identidade de usuário implicada pela chamada.

Detalhes chave de implementação

Auth handlers — O gateway precisa de dois caminhos de handler de auth:

  • Agentic path (AgenticUserAuthorization): Usado quando a atividade chega através do canal agentic do M365 Copilot. Requer tanto abs_oauth_connection_name quanto obo_connection_name.
  • Connector path (UserAuthorization): Usado quando a atividade chega através do conector padrão do Teams/Bot. Requer apenas abs_oauth_connection_name.
auth_handlers = {
    "service_agentic": AuthHandler(
        auth_type="AgenticUserAuthorization",
        abs_oauth_connection_name="SERVICE_CONNECTION",
        obo_connection_name="SERVICE_OBO_CONNECTION",  # pode ser o mesmo que o abs
        scopes=["api://<service-client-id>/access_as_user"],
    ),
    "service_connector": AuthHandler(
        auth_type="UserAuthorization",
        abs_oauth_connection_name="SERVICE_CONNECTION",
        obo_connection_name="",
        scopes=["api://<service-client-id>/access_as_user"],
    ),
}

Tratamento de invoke — O Copilot envia atividades invoke durante o handshake de troca de token SSO. O gateway deve tratá-las graciosamente (retornar sem erro) ou o fluxo de login quebra.

Mapeamento de sessão — Use context.activity.conversation.id como chave de sessão ao forwardar para o serviço. Isso garante que a mesma conversa do Copilot sempre mapeie para a mesma sessão agentic.

Healthz bypass — A health probe do ACA atinge /healthz. Ignore o middleware JWT para este caminho ou a probe falhará.

Escolhas de tecnologia — O gateway usa microsoft-agents-hosting-fastapi para o adapter Bot SDK, microsoft-agents-authentication-msal para troca de token baseada em MSAL e httpx para chamadas HTTP de forwarding ao serviço. Você pode substituir qualquer Bot SDK ou linguagem, desde que lide com o mesmo protocolo.

Componente 2: O Serviço Agentic (Stateful, Framework-Agnostic)

O serviço é uma API HTTP comum. Ele pode não saber nada sobre Bot Framework, Teams ou formas de atividade específicas do Copilot, mas se possui acesso delegado a downstream, deve validar o bearer de serviço recebido em sua própria fronteira. Ele recebe o token scoped, valida, vincula claims do usuário a partir desse token validado, executa sua lógica agentic e retorna uma resposta.

Modelo de confiança do serviço: O serviço é a fronteira de confiança de negócios/dados. Ele deve confiar no token bearer validado que recebe, não apenas em headers de conveniência. Headers forwardados X-User-* são metadados opcionais e nunca devem ser a única base para scoping de usuário ou OBO downstream.

Serviço Agentic

Requisitos mínimos do serviço

Requisito O que significa Por quê
Validar bearer de entrada Ler e validar o header Authorization: Bearer Necessário antes de usar o token para OBO, ownership ou autorização
Extrair claims do token validado Decodificar oid, tid, upn/preferred_username, scopes, etc. Necessário para isolamento de dono de sessão e trilha de auditoria
Identidade de sessão/thread Manter estado da conversa entre turnos O Copilot espera conversas multi-turn
Troca de mensagens Aceitar texto do usuário, retornar uma resposta Função central
OBO #2 (se acesso delegado for necessário) acquire_token_on_behalf_of() do MSAL com a assertion forwardada Apenas se estiver chamando APIs downstream como o usuário
Ingress hardening Preferir ingress interno ou controles de rede equivalentes quando prático Reduz superfície de ataque, mas não substitui validação de token no serviço

Padrão ContextVar para a assertion OBO

A assertion JWT validada (a string de token bruta do gateway) deve estar disponível profundamente na pilha de chamadas quando uma chamada OBO downstream acontece — potencialmente várias camadas abaixo do handler HTTP. Use contextvars.ContextVar em vez de passá-la através de cada assinatura de função:

from contextvars import ContextVar, Token as CtxToken

_USER_ASSERTION: ContextVar[str | None] = ContextVar("user_assertion", default=None)
_USER_CLAIMS: ContextVar[TokenClaims | None] = ContextVar("user_claims", default=None)

def bind_identity(assertion: str, claims: TokenClaims):
    return _USER_ASSERTION.set(assertion), _USER_CLAIMS.set(claims)

def reset_identity(a_tok: CtxToken, c_tok: CtxToken):
    _USER_ASSERTION.reset(a_tok)
    _USER_CLAIMS.reset(c_tok)

# No middleware, após validar o bearer:
a_tok, c_tok = bind_identity(raw_jwt, validated_claims)
try:
    response = await call_next(request)
finally:
    reset_identity(a_tok, c_tok)  # sempre limpar

Por que ContextVar? — É async-safe (cada requisição concorrente ganha seu próprio escopo), framework-agnostic (funciona com FastAPI, Flask, Django, asyncio puro) e evita threadar a assertion por toda a sua pilha agentic.

OBO #2 para serviços downstream

Quando sua lógica agentic precisa chamar uma API downstream como o usuário:

import msal

app = msal.ConfidentialClientApplication(
    client_id=SERVICE_CLIENT_ID,
    client_credential=SERVICE_CLIENT_SECRET,
    authority=f"https://login.microsoftonline.com/{TENANT_ID}",
)

result = app.acquire_token_on_behalf_of(
    user_assertion=_USER_ASSERTION.get(),  # da ContextVar
    scopes=["<downstream-resource-id>/.default"],  # ex.: Graph, Databricks, sua API
)
downstream_token = result["access_token"]

Exemplos comuns de escopos downstream:

Downstream Scope
Microsoft Graph https://graph.microsoft.com/.default
Azure Databricks 2ff814a6-3304-4ab8-85cb-cd0e6f879c1d/.default
Azure SQL https://database.windows.net/.default
API interna customizada api://<their-app-id>/.default
Servidor MCP atrás do Entra api://<mcp-app-id>/.default

O padrão é idêntico para qualquer recurso protegido pelo Entra — apenas o escopo muda.

Gerenciamento de sessão

Session store mínimo viável para MVP (in-memory, réplica única):

  • CreatePOST /api/chat/sessions → retorna session_id
  • Send messagePOST /api/chat/sessions/{id}/messages → retorna reply
  • Get sessionGET /api/chat/sessions/{id} → retorna histórico de turnos
  • Owner isolation — Toda sessão tem um owner_id do claim oid do JWT validado. Rejeitar acesso cross-user com 403.
  • Turn windowing — Limitar turnos armazenados a max_turns * 2 entradas para evitar crescimento ilimitado de memória.
  • Concurrency lockasyncio.Lock() por sessão previne turnos intercalados.

Sua lógica agentic vai aqui

A API do serviço é uma fronteira limpa. Dentro dela, use o que quiser:

  • Microsoft Agent Framework — HandoffBuilder, ConcurrentBuilder, etc.
  • LangChain / LangGraph — chains, graphs, tool calling
  • Semantic Kernel — planners e plugins
  • Código customizado — chamadas diretas ao SDK OpenAI/Azure OpenAI com tool loops
  • Qualquer outro framework — CrewAI, AutoGen, etc.

Contrato de API de referência

Se estiver construindo um novo serviço do zero, este contrato funciona bem com o gateway:

POST /api/chat/sessions/{session_id}/messages
Authorization: Bearer <service-scoped-token>
Content-Type: application/json
{"text": "user message"}
200 {"session_id": "...", "reply": "...", "turns": [...]}
401 (bad/missing token)
404 (session not found — gateway auto-creates and retries)

No entanto, se você tem um serviço existente com um contrato diferente, não precisa mudá-lo. Em vez disso, adapte o service client do gateway para falar a API do seu serviço. As três capacidades essenciais são identidade de sessão, troca de mensagens e autenticação — os caminhos de URL e as formas de payload são flexíveis.

Componente 3: O Pacote de App M365

App Package

O pacote de app é um ZIP contendo um manifest e ícones que registra o gateway como um Custom Engine Agent no Microsoft 365.

Campos críticos do manifest

{
  "bots": [{
    "botId": "<BOT_APP_ID>",
    "scopes": ["personal"]
  }],
  "webApplicationInfo": {
    "id": "<BOT_SSO_APP_ID>",
    "resource": "api://botid-<BOT_APP_ID>"
  },
  "copilotAgents": {
    "customEngineAgents": [{
      "type": "bot",
      "id": "<BOT_APP_ID>",
      "functionsAs": "agentOnly"
    }]
  },
  "validDomains": [
    "<gateway-host>.azurecontainerapps.io",
    "token.botframework.com"
  ]
}

Sequência de deploy

Siga esta ordem exata. Cada passo depende dos outputs anteriores.

Deployment Sequence

Passo O quê Depende de
1 Criar service app + bot app no Entra ID Acesso ao tenant
2 Conceder consentimento admin para ambas as cadeias de permissão delegada Passo 1
3 Criar recurso Azure Bot, configurar conexão OAuth (SERVICE_CONNECTION) Passos 1-2
4 Provisionar recursos downstream (bancos, APIs, warehouses) Independente
5 Construir imagem container, fazer deploy do Serviço Agentic no ACA Passos 1-4
6 Validar /healthz, criação de sessão, primeiro turno, OBO para downstream Passo 5
7 Construir imagem container, fazer deploy do M365 Gateway no ACA Passos 1-3, 5
8 Validar /healthz, forwarding gateway-para-serviço Passos 6-7
9 Construir manifest.json + ícones em pacote ZIP Passos 1, 7
10 Fazer upload do ZIP no centro de admin do M365 ou Graph API Passo 9
11 Definir endpoint de mensagens do Azure Bot para https://<gateway>/api/messages Passos 3, 7
12 Abrir M365 Copilot, enviar mensagem, verificar fluxo de ponta a ponta Passos 10-11

Referência de variáveis de ambiente

Serviço Agentic

Variável Exemplo Propósito
AZURE_TENANT_ID b5d67878-... Entra tenant (para OBO #2)
SERVICE_CLIENT_ID <service-app-id> Service app registration (para OBO #2)
SERVICE_CLIENT_SECRET <secret> Credencial cliente OBO do serviço
DOWNSTREAM_OBO_SCOPE <resource-id>/.default Escopo do recurso downstream para OBO #2
AZURE_OPENAI_ENDPOINT https://....cognitiveservices.azure.com Endpoint LLM
AZURE_OPENAI_DEPLOYMENT gpt-4o Nome do deployment do modelo

M365 Gateway

Variável Exemplo Propósito
AZURE_TENANT_ID b5d67878-... Entra tenant
BOT_APP_ID <bot-app-id> Bot registration
BOT_APP_PASSWORD <secret> Client secret do Bot
SERVICE_BASE_URL https://my-service.azurecontainerapps.io Endpoint do serviço
SERVICE_API_SCOPE api://<service-app-id>/access_as_user Escopo alvo do OBO #1
SERVICE_EXPECTED_AUDIENCE api://<service-app-id> Validação de audience JWT

Checklist de segurança

flowchart LR
    subgraph "Defense in Depth"
        V1["Gateway channel validation<br/>(Bot/channel token path)"]
        V2["Service bearer validation<br/>(audience + issuer + signature)"]
        V3["ContextVar isolation<br/>(per-request, async-safe)"]
        V4["Session owner check<br/>(oid-based)"]
        V5["Data access control<br/>(scope-limited queries)"]
    end
    V1 --> V2 --> V3 --> V4 --> V5
  • Gateway channel auth enforced — Tráfego de canal é validado antes do forwarding
  • Service JWT audience validated — Serviço rejeita tokens com aud errado
  • Service JWT issuer validated — Serviço aceita apenas as URIs STS do tenant pretendido
  • Service JWT signature verified — RS256 via endpoint JWKS, não chaves fixas
  • Service ingress hardened — Preferir ingress interno ou restrito quando prático
  • ContextVar reset in finally block — Previne vazamento de assertion entre requisições
  • Session owner isolationowner_id do claim oid validado, 403 em mismatch
  • Downstream data access scoped — Usar queries parametrizadas, operações pré-definidas ou row-level security; evitar expor interfaces de query cruas ao agente
  • Secrets stored securely — Client secrets como ACA secrets (secretref:), não env vars em texto puro
  • Health endpoint unauthenticated/healthz ignora middleware JWT
  • Invoke activities handled — Gateway retorna limpo para invokes do handshake SSO
  • Admin consent granted — Ambas as cadeias de permissão OBO têm consentimento do admin do tenant

Problemas comuns e troubleshooting

Sintoma Causa Correção
AADSTS65001: not consented Consentimento admin ausente para permissões delegadas Conceder consentimento admin para ambas as cadeias OBO
The provided token is not exchangeable Conexão OAuth do Bot mal configurada ou cache SSO obsoleto Recriar conexão OAuth; abrir nova conversa no Copilot
OBO #2 retorna invalid_grant Assertion do usuário expirada ou audience mismatch no service app Garantir requestedAccessTokenVersion: 2 e audience correto
Gateway retorna 401 para o Copilot Auth de canal do gateway ou aquisição de token wrapper falha Verificar config de auth do Bot/canal e wiring dos auth handlers do gateway
Serviço retorna 401/403 para o gateway Validação de bearer do serviço rejeita o token forwardado Verificar SERVICE_EXPECTED_AUDIENCE, issuer, tenant e lógica de refresh de chave de assinatura
Session 403 no segundo turno owner_id da sessão não corresponde ao oid do token validado no turno seguinte Verificar se o mesmo usuário logado está presente e o serviço deriva ownership do bearer validado, não de headers mutáveis forwardados
/healthz retorna 401 Endpoint de health não excluído do middleware de auth Adicionar exclusão de caminho para /healthz
Atividades invoke mostram erro ao usuário Handler de invoke ausente no gateway Adicionar rota no-op para activity.type == "invoke" com is_invoke=True

Estendendo o padrão

Como adicionar um novo serviço downstream?

Seja o downstream uma REST API, um banco de dados (SQL, Cosmos DB), um servidor MCP ou uma plataforma SaaS — o padrão OBO é idêntico, desde que o recurso seja protegido pelo Entra ID:

  1. Adicione uma permissão delegada ao service app registration para o novo recurso
  2. Conceda consentimento admin
  3. Adicione uma nova função acquire_X_access_token() usando a mesma assertion da ContextVar com o escopo do novo recurso (ex.: https://graph.microsoft.com/.default)
  4. Chame-a de sua lógica agentic quando o novo downstream for necessário

Cenários comuns:

  • REST APIs — Chamar com token Bearer no header Authorization
  • Bancos de dados (Azure SQL, Databricks, Cosmos DB) — Usar o token OBO como credencial de auth para o cliente de banco ou connection string
  • Servidores MCP — Se o servidor MCP estiver atrás do Entra, passar o token OBO; se usar um esquema de auth diferente, trocar ou pontear a credencial conforme necessário
  • Microsoft Graph — OBO para https://graph.microsoft.com/.default para perfil do usuário, e-mail, calendário, arquivos, etc.

Como funciona com um serviço agentic existente?

Se você já tem um serviço agentic funcional que não foi projetado para M365:

  1. Mantenha o contrato do serviço estável quando possível. Seu contrato de API, modelo de sessão e runtime agentic geralmente podem permanecer como estão. Se ele já não valida o bearer do serviço, adicione isso na fronteira do serviço antes de confiar em acesso delegado downstream.
  2. Crie o gateway com uma classe service client que mapeia conversas do Copilot para o modelo de sessão do seu serviço, remodela payloads de mensagens e anexa o token OBO na forma que o serviço espera. O gateway também lida com auth de canal e pode forwardar claims do usuário como headers de metadados opcionais.
  3. Prefira ingress restrito para o serviço para que não fique amplamente exposto. Isso é hardening recomendado, mas não substitui a validação de bearer no lado do serviço.
  4. Registre os apps no Entra como descrito acima. O scope access_as_user do service app é o alvo do OBO #1 do gateway. Se o serviço já tem um registro de app no Entra, adicione o scope access_as_user e pré-autorize o bot app.
  5. Se o serviço chamar APIs downstream e precisar de acesso delegado, faça-o validar o token Bearer forwardado e use essa assertion validada como user_assertion para OBO #2. Se o serviço usar API keys ou managed identity para chamadas downstream (não delegadas), OBO #2 não é necessário.

Esta abordagem permite trazer qualquer serviço agentic — independente de framework, linguagem ou hospedagem — para o M365 Copilot escrevendo apenas a camada de gateway.

E se precisar escalar além de uma réplica?

Substitua o session store in-memory por Redis, Cosmos DB ou outro store distribuído. O padrão ContextVar + OBO não é afetado — é por requisição, não por réplica.

Como adicionar um segundo agente voltado para o Copilot?

Implante um segundo par gateway + serviço. Cada um recebe seus próprios registros de app e recurso Bot. O pacote de app M365 pode declarar múltiplos bots ou você pode publicar pacotes separados.

Para um exemplo prático desta arquitetura, veja a implementação de referência: james-tn/dbx-mcp-copilot. O repositório demonstra como o gateway, o serviço agentic e o fluxo de token OBO delegado funcionam juntos em um deployment real voltado para Microsoft 365 Copilot e Teams.

Perguntas Frequentes

  • Preciso reconstruir meu agente para usar declarative agents no M365 Copilot?
    Não. A abordagem apresentada permite manter seu serviço agentic intacto. Basta criar um gateway stateless que traduz o protocolo Bot Framework para a API do seu serviço. Você não precisa modificar a lógica de orquestração, o framework ou o gerenciamento de sessão do seu agente original.

  • Como funciona a segurança do token do usuário nesse fluxo?
    O token do usuário nunca sai da cadeia OBO. O gateway recebe um token de canal, faz a primeira troca OBO por um token scoped para seu serviço, e repassa esse token. Seu serviço valida o bearer recebido e, se precisar chamar downstreams, faz uma segunda troca OBO. Nenhum serviço armazena senhas ou tokens originais.

  • Quantas app registrations no Entra ID são necessárias?
    Duas: uma para o serviço agentic (que expõe o scope access_as_user) e outra para o gateway/bot (que consome esse scope). Ambas exigem consentimento do admin do tenant. O gateway também precisa de uma OAuth connection configurada no Azure Bot Resource.

  • Meu serviço usa um framework diferente (ex.: LangChain, custom). Funciona?
    Sim. A arquitetura é framework-agnostic. O gateway precisa apenas que seu serviço suporte três capacidades: identidade de sessão (session ID), troca de mensagens (entrada/saída de texto) e autenticação via bearer token. O gateway adapta a chamada para qualquer API que seu serviço exponha.

  • E se meu serviço precisar escalar além de uma réplica?
    Substitua o session store in-memory por Redis, Cosmos DB ou outro store distribuído. O padrão ContextVar + OBO não é afetado, pois é por requisição, não por réplica. O gateway escala horizontalmente sem estado.


Artigo originalmente publicado por JamesN em Azure Updates - Latest from Azure Charts.

Gostou? Compartilhe:
Precisa de ajuda?Fale com nossos especialistas 👋
Avatar Walcew - Headset