Teams MCP - Security
4 min read
This document describes the security architecture, cryptographic decisions, and threat model for the Teams MCP Server.
Security Layers

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 |
|
Why AES-GCM:
Provides both confidentiality and integrity (authenticated encryption)
Prevents tampering with ciphertext
Industry standard for token encryption
Token Lifecycle:
Microsoft issues tokens during OAuth flow
Tokens encrypted immediately before database write
Tokens decrypted only when needed for Graph API calls
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.

PKCE Protection:
Prevents authorization code interception attacks
Required for all OAuth flows (no exceptions)
Uses
S256challenge 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:
RFC 7636 - PKCE - Proof Key for Code Exchange
OAuth 2.1 - OAuth 2.1 specification
RFC 6749 - OAuth 2.0 - OAuth 2.0 Authorization Framework
Refresh Token Rotation
Refresh tokens are rotated on every use with family-based revocation:

Token Family Revocation:
Each OAuth session has a token_family identifier. When a refresh token is used:
Server checks if token was already used
If reused → entire family revoked (possible theft)
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:

Validation Details:
MICROSOFT_WEBHOOK_SECRETis a 128-character random stringSent to Microsoft when creating subscriptions
Returned in every webhook payload
Requests with invalid
clientStateare rejected
Secret Management
Required Secrets
Secret | Purpose | Rotation Impact |
|---|---|---|
| Encrypt Microsoft tokens | All users must reconnect |
| Sign MCP JWTs | All sessions invalidated |
| Authenticate with Entra ID | Update and restart |
| Validate webhooks | Recreate all subscriptions |
Rotation Procedures
ENCRYPTION_KEY Rotation:
There is no zero-downtime rotation—key change invalidates all stored tokens
Deploy with new key
All users must reconnect to MCP server
Consider warning users before rotation
AUTH_HMAC_SECRET Rotation:
Change secret and deploy
All MCP sessions immediately invalidated
Clients will re-authenticate automatically
MICROSOFT_CLIENT_SECRET Rotation:
Create new secret in Entra ID
Update Kubernetes secret
Restart pods
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:
Generating new 128-character secret:
openssl rand -hex 64Manually deleting all subscriptions via Microsoft Graph API
Having all users reconnect to MCP server to trigger subscription recreation
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
1incompleteENCRYPTION_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 failuresRelated Documentation
Authentication Architecture - Token isolation, storage, and encryption
Microsoft OAuth Setup Flow - OAuth flow sequence
Microsoft Token Refresh Flow - Token refresh sequence
Architecture - Data model and infrastructure
Configuration - Secret configuration
FAQ - Frequently asked questions
Standard References
RFC 7636 - PKCE - Proof Key for Code Exchange
RFC 6749 - OAuth 2.0 - OAuth 2.0 Authorization Framework
OAuth 2.1 - OAuth 2.1 specification
NIST SP 800-38D - AES-GCM specification
MDN Web Crypto API - Web cryptography reference