# Teams MCP - Security

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 | `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](https://oauth.net/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 `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:**
- [RFC 7636 - PKCE](https://datatracker.ietf.org/doc/html/rfc7636) 
- [OAuth 2.1](https://oauth.net/2.1/) 
- [RFC 6749 - OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749)

## 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:
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`:

**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

## Write Surface (Send Tools)

The `send_chat_message` and `send_channel_message` tools are **write operations** — they post messages to Teams chats and channels as the signed-in user. This is the first write capability in the Teams MCP Server and is worth stating explicitly.

These tools operate under the same delegated-OAuth and token-isolation model as all read operations: Microsoft tokens are stored encrypted on the server and never sent to the client, and the agent can only post to chats and channels that the signed-in user can access. The `ChannelMessage.Send` and `ChatMessage.Send` permissions cover sending only; they do not grant read access to message content.

## Channel Message Admin Consent

Reading channel message content (`get_channel_messages`, `search_messages` with `detail=full`) requires the `ChannelMessage.Read.All` permission, which **requires admin consent** because channel messages may contain sensitive organisational content. Without admin consent for this permission, the send and list tools remain fully functional.

See [Permissions](https://docs.unique.ai/it-operators/integrations-connectors/teams-mcp/teams-mcp-technical-manual/teams-mcp-permissions) for the full consent breakdown and the distinction between user-consentable and admin-consent-required scopes.

## 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.

## Security Checklist for Operators

1. `ENCRYPTION_KEY` is a cryptographically random 64-character hex string
2. `AUTH_HMAC_SECRET` is a cryptographically random 64-character hex string
3. `MICROSOFT_WEBHOOK_SECRET` is a cryptographically random 128-character string
4. All secrets stored in Kubernetes secrets (not ConfigMaps)
5. Network policies restrict pod-to-pod communication
6. TLS termination configured at ingress
7. Log aggregation excludes sensitive fields (tokens are auto-redacted)
8. Monitoring alerts configured for authentication failures

## Standard References

- [RFC 7636 - PKCE](https://datatracker.ietf.org/doc/html/rfc7636)
- [RFC 6749 - OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749)
- [OAuth 2.1](https://oauth.net/2.1/)
- [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) 
- [MDN Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API)
