Partner Co Azure & M365 Architecture

identity model · bot infrastructure · client onboarding

Partner Co uses two separate Azure app registrations for two completely different jobs. One gives you Graph API access to act on behalf of client employees. The other is just an identity for Teams bots to receive messages. This page explains how they work, what's shared across clients, and what you create per client.

1 — The Two Apps
Partner Co App
aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Act as the user via Graph API

Client employee logs into app.partner.example.com → OAuth consent → you get a refresh token → your platform can read/send their email, manage calendar, access OneDrive on their behalf.

  • Multi-tenant (any company's users)
  • Permissions: Mail.ReadWrite, Calendars.ReadWrite, etc.
  • Admin consent required (powerful permissions)
  • One app for ALL clients
Bot App (per bot)
e.g. 11111111-... (BotOne)
Receive Teams messages via Bot Framework

User @mentions the bot in Teams → Microsoft signs a JWT → POSTs to your webhook → your code validates the JWT and processes the message. No Graph API involved.

  • Multi-tenant app registration (consented per tenant)
  • Permissions: NONE (zero Graph permissions)
  • Admin consent: maybe not needed
  • One app per bot identity
Partner Co App Bot App
Calls Graph API? Yes — email, calendar, files No
API permissions? Many (Mail.ReadWrite, etc.) None
If secret leaks? Attacker can read client email Attacker can impersonate bot messages only
How many? One for all clients One per bot identity
Rotation cycle Shared across all clients Independent per bot
Don't merge them. If you reused the Partner Co App for bot identity, a bot secret leak would also expose Graph API access to all client data. Separate apps = separate blast radius.
2 — The Bot Identity Trio

Three Azure/Teams resources are linked by the same App ID. All three are required for a Teams bot to work.

1 · Identity

App Registration

Entra ID

A client ID + secret. Just an identity — it has no idea it's a bot. Like a passport.

uses
2 · Routing

Bot Service Resource

Azure

The glue. Tells Microsoft: "When someone messages this bot, POST to this webhook URL."

enables
3 · UI

Teams App Manifest

ZIP file

Name, icon, and botId. Uploaded to client's Teams Admin Center. What users see.

3 — Service Principals

App Registration

Lives in Partner Co's tenant.
Master definition of the app.
"I am BotOne, here's my ID and secret."

= Your passport

admin consent

Service Principal

Created in the client's tenant.
Local copy that says: "I know about this app and I've approved it."

= A visa stamp

Without a service principal, the client's tenant has no idea your app exists. With it, the tenant recognizes the app and can enforce policies on it (conditional access, audit logs, etc.).

For the Partner Co App: the service principal carries all granted permissions (Mail.ReadWrite, etc.).
For a bot app: the service principal is mostly just "I acknowledge this app exists" — since there are zero permissions.

4 — Message Flow

How a Teams message reaches an AgentPlatform bot and comes back.

sequenceDiagram
    participant U as Teams User
    participant T as Teams Cloud
    participant B as Azure Bot Service
    participant C as Caddy
    participant O as AgentPlatform
    participant AI as Claude API

    U->>T: @BotOne summarize the sales report
    T->>B: Route to bot via App ID
    B->>B: Sign JWT with Microsoft private keys
    B->>C: POST webhook URL with signed JWT
    C->>O: Reverse proxy to container port
    O->>O: Validate JWT signature, aud, tid claims
    O->>AI: Process with agent engine
    AI-->>O: AI response
    O-->>B: Reply via Bot Framework SDK
    B-->>T: Deliver to conversation
    T-->>U: BotOne replies in thread
      
Security. The webhook URL is public, but protected by Microsoft-signed JWTs. An attacker who discovers the URL cannot forge a valid token because they don't have Microsoft's private signing keys. The AgentPlatform config also enforces tenantId — only messages from the configured client tenant are accepted.
5 — Shared vs. Per-Client
Create Once
  • Partner Co App (aaaaaaaa)
  • M365 tenant (partner.example.com)
  • Azure subscription
  • Resource group (dogood-bots)
  • VPS (203.0.113.10)
  • Caddy reverse proxy
  • Partner Center enrollment
  • Partner Co Admins group
Per Bot Identity
  • App Registration
  • Bot Service resource
  • Teams channel enable
  • Teams App Manifest ZIP

~5 min Azure work

Per Client Company
  • Admin consent (Partner Co App)
  • Admin consent (Bot App?)
  • Teams app sideload
  • AgentPlatform container
  • DNS A record
  • Caddyfile entry
  • tenantId in config
  • GDAP relationship
6 — Admin Consent Flows

Each client's admin clicks up to two consent URLs. These are independent — Graph access and Teams bot access are separate concerns.

7 — The "SingleTenant" vs "Multi-tenant" Confusion

These terms mean different things in different contexts. Here's what actually matters.

Setting Where What it controls
Multi-tenant app registration Entra ID The app can be consented into any tenant (consent URL creates a service principal there)
SingleTenant bot resource Azure Bot Service How the bot authenticates outbound to Bot Framework (uses Partner Co's creds). Does NOT restrict who can message it.
tenantId in AgentPlatform config Your VPS Inbound access control — JWT tid claim must match this tenant, or the message is rejected.
"SingleTenant" on the Bot Service does NOT restrict which users can message the bot. It only controls outbound auth. Multi-tenant bot creation was deprecated by Microsoft in July 2025, so SingleTenant is the only option now. User restriction is enforced at the AgentPlatform config level.
8 — Open Questions

Is bot admin consent actually required?

The bot app has zero API permissions. The Teams app sideload might create the service principal automatically, making the consent URL redundant.

Test plan: Skip the bot consent URL during Client B onboarding (Wednesday 3/4). Just do the sideload. If the bot responds, drop this step from the playbook. If not, have client admin click the consent link and retry.

One bot app per client vs. shared platform app?

Current approach: one App Registration + Bot Service per bot identity (BotOne, BotTwo, etc.). This gives per-client naming, isolation, and independent secret rotation.

A shared approach (one app + routing layer) would reduce Azure resources but sacrifice isolation and add complexity. Verdict: Per-bot is better at our scale (2-10 clients). Revisit at 50+.