Teams MCP - Security

4 min read

This document describes the security architecture, cryptographic decisions, and threat model for the Teams MCP Server.

Security Layers

embedded_33babb024412e5024075a90ecfc1dcbb.png

Token Security

Microsoft Tokens (Encrypted at Rest)

Microsoft access and refresh tokens are stored encrypted using AES-256-GCM:

Aspect

Implementation

Algorithm

AES-256-GCM (authenticated encryption)

Key Size

256 bits (64 hex characters)

IV

Random 12 bytes per encryption

Authentication

Built-in with GCM mode

Key Storage

ENCRYPTION_KEY environment variable

Why AES-GCM:

  • Provides both confidentiality and integrity (authenticated encryption)

  • Prevents tampering with ciphertext

  • Industry standard for token encryption

Token Lifecycle:

  1. Microsoft issues tokens during OAuth flow

  2. Tokens encrypted immediately before database write

  3. Tokens decrypted only when needed for Graph API calls

  4. Tokens re-encrypted after refresh

MCP Tokens (Hashed for Validation)

MCP access and refresh tokens use a different approach:

Token Type

Storage

Validation

Access Token

SHA-256 hash

Cache-first, then database lookup

Refresh Token

SHA-256 hash

Database lookup with family check

Why Hashing (not Encryption):

  • Tokens are opaque JWTs—the server doesn't need to read them

  • Hash comparison is sufficient for validation

  • Reduces attack surface (no decryption key needed)

OAuth 2.1 with PKCE

The MCP OAuth implementation follows OAuth 2.1 with mandatory PKCE. This diagram shows the MCP OAuth flow where the server issues opaque JWT tokens to the client after the Microsoft OAuth flow completes on the server.

Important: The tokens issued to the client in this flow are opaque JWTs for MCP authentication, not Microsoft tokens. Microsoft tokens remain on the server and are never sent to clients.

embedded_a4b7880a3f1dc5f83eed0fc118530136.png

PKCE Protection:

  • Prevents authorization code interception attacks

  • Required for all OAuth flows (no exceptions)

  • Uses S256 challenge method (SHA-256)

Token Separation:

  • Microsoft tokens: Stay on server, encrypted at rest, used for Graph API calls

  • MCP tokens: Opaque JWTs issued to client, used for MCP API authentication

References:

Refresh Token Rotation

Refresh tokens are rotated on every use with family-based revocation:

embedded_94083ec3bb6a493751f9b2b279d15fe8.png

Token Family Revocation:

Each OAuth session has a token_family identifier. When a refresh token is used:

  1. Server checks if token was already used

  2. If reused → entire family revoked (possible theft)

  3. If valid → token marked used, new token issued with same family

This detects scenarios where an attacker obtains a refresh token and uses it, but the legitimate client also tries to use the original.

Webhook Validation

Microsoft Graph webhooks are validated using clientState:

embedded_98ea168d2c5ba2f7778c33497d06b84e.png

Validation Details:

  • MICROSOFT_WEBHOOK_SECRET is a 128-character random string

  • Sent to Microsoft when creating subscriptions

  • Returned in every webhook payload

  • Requests with invalid clientState are rejected

Secret Management

Required Secrets

Secret

Purpose

Rotation Impact

ENCRYPTION_KEY

Encrypt Microsoft tokens

All users must reconnect

AUTH_HMAC_SECRET

Sign MCP JWTs

All sessions invalidated

MICROSOFT_CLIENT_SECRET

Authenticate with Entra ID

Update and restart

MICROSOFT_WEBHOOK_SECRET

Validate webhooks

Recreate all subscriptions

Rotation Procedures

ENCRYPTION_KEY Rotation:

  1. There is no zero-downtime rotation—key change invalidates all stored tokens

  2. Deploy with new key

  3. All users must reconnect to MCP server

  4. Consider warning users before rotation

AUTH_HMAC_SECRET Rotation:

  1. Change secret and deploy

  2. All MCP sessions immediately invalidated

  3. Clients will re-authenticate automatically

MICROSOFT_CLIENT_SECRET Rotation:

  1. Create new secret in Entra ID

  2. Update Kubernetes secret

  3. Restart pods

  4. Delete old secret from Entra ID

MICROSOFT_WEBHOOK_SECRET Rotation:Currently not possible - There is no easy way to invalidate all existing subscriptions that were created with the old secret. Existing subscriptions have the old secret as clientState and will fail validation if the secret changes, but there's no automated mechanism to recreate all subscriptions.

Note: Automated rotation might be part of a future release.

If rotation becomes necessary, it would require:

  1. Generating new 128-character secret: openssl rand -hex 64

  2. Manually deleting all subscriptions via Microsoft Graph API

  3. Having all users reconnect to MCP server to trigger subscription recreation

  4. Updating Kubernetes secret and deploying

Important: Recreation may miss transcripts created during the gap between deletion and recreation. See Why are subscriptions renewed instead of recreated? for details.

Security Checklist for Operators

1incomplete ENCRYPTION_KEY is a cryptographically random 64-character hex string2incomplete AUTH_HMAC_SECRET is a cryptographically random 64-character hex string3incomplete MICROSOFT_WEBHOOK_SECRET is a cryptographically random 128-character string4incomplete All secrets stored in Kubernetes secrets (not ConfigMaps)5incomplete Network policies restrict pod-to-pod communication6incomplete TLS termination configured at ingress7incomplete Log aggregation excludes sensitive fields (tokens are auto-redacted)8incomplete Monitoring alerts configured for authentication failures

Standard References

Last updated