Skip to content

feat(teams): Each user signs in, so a chat agent acts as each user (1.0.0) - #4164

Merged
crazywoola merged 2 commits into
langgenius:mainfrom
ForeignKeyCN:feat/teams-per-user-signin
Nov 2, 2026
Merged

crazywoola merged 2 commits into
langgenius:mainfrom
ForeignKeyCN:feat/teams-per-user-signin

Conversation

@ForeignKeyCN

Copy link
Copy Markdown
Contributor

Summary

Same feature as the Outlook plugin's #4163, for Teams. Today a Teams authorization is shared: everyone using the tool posts and reads as the one person who signed in from the console. For a chat agent that many people use ("what's new in my team?", "set up a meeting"), each person should act as themselves.

Each user signs in (new API Key credential; Teams so far had OAuth only)

  • The credential holds only the app registration (tenant, client ID, secret, cloud) and the Sign-in Callback URL; no user token is stored in Dify's credential.
  • The first time a chat user needs Teams, the tool returns a Microsoft sign-in link (authorization code + PKCE + random state). A new plugin endpoint (GET /teams/callback) receives the browser's return and only stores the one-time code under its state; it never sees the client secret or the PKCE verifier.
  • The tool redeems the code and keeps the user's tokens in plugin storage, keyed by a hash of client_id | Dify user ID and Fernet-encrypted with a key derived (HKDF) from the client secret. Tokens never appear in tool output or logs; the user ID comes from Dify's runtime, never from the LLM.
  • Background refresh; sign-in is asked again only when the refresh token is rejected. Rotating the client secret signs everyone out.
  • Sign Out of Teams tool, named teams_sign_out: the new Agent exposes tools by their bare name and rejects duplicates, so it cannot share a name with Outlook's outlook_sign_out.
  • The Signed in page warns about forwarded sign-in links and offers to cancel; the first answer after signing in names the account used.
  • Validation checks the fields, rejects a localhost callback URL (what Dify shows while ENDPOINT_URL_TEMPLATE has its default) and a path not ending in /teams/callback, and for a specific tenant proves the client ID and secret with a client-credentials token that is then discarded.
  • The delegated scopes are the OAuth credential's (TeamsProvider._SCOPE); a test keeps the two equal.

There is no app-only mode: Microsoft Graph does not let app-only tokens post messages or read chats. All 21 tools are unchanged except that they pass themselves to resolve_graph_context, which now returns the per-user token in this mode.

The sign-in code (tools/user_auth.py, endpoints/signin_callback.py) is the same as Outlook's apart from the scopes, the endpoint path and the key-derivation label, so that the two plugins' stored tokens can never be read by each other.

Docs

  • README: choosing between OAuth and Each user signs in, the permission list as a table with what to search for in the Entra portal (none is named "Teams", which trips people up), step-by-step setup with screenshots, what users see, and a prompt for an agent that uses Outlook and Teams together. GUIDE and PRIVACY updated.

Existing OAuth authorizations keep working unchanged; the version goes to 1.0.0 for the new mode and endpoint, matching Outlook.

Release Notes

  • Each user signs in: a new authorization for chat agents. Each person chatting with the agent signs in once with their own Microsoft account, and the Teams tools act as that person. Needs the new sign-in endpoint (plugin details → Endpoints) registered as a Web redirect URI on the app.
  • New Sign Out of Teams tool for this mode, to switch accounts.
  • Users' tokens are stored encrypted in the plugin's storage and never shown to the agent.
  • Existing OAuth authorizations are unchanged.

Change Type

  • Documentation / non-plugin change
  • Non-LLM plugin (tools, extensions, datasource, etc.)
  • LLM plugin

Screenshots / Videos

Agent on a self-hosted Dify with Outlook and Teams, both using Each user signs in:

Sign-in link, then the user's teams Online meeting as the user Sign out of Teams, then the link again
Authorization form Callback URL and cloud

Testing

  • Unit tests: 136 passed (pytest): full sign-in, cached and refreshed tokens, per-user isolation and encryption at rest, secret rotation, revoked vs. temporarily failing refresh, the callback rejecting unknown/expired/forged states and the cancel link, per-user validation (secret check, localhost, wrong path), the scopes matching OAuth's, and every tool's output variables.
  • End to end on a self-hosted Dify (Community 1.16.1) with an Agent app that also has Outlook 1.0.0 (feat(outlook): Each user signs in, so a chat agent works on each user's own mailbox (1.0.0) #4163), against a Microsoft 365 tenant: "List my teams" → Teams sign-in link → Microsoft sign-in → callback page → the user's team listed; an online meeting created with its join link; "Sign out of Teams" → the next request asks to sign in again, while the Outlook sign-in stays (checked in plugin storage).
  • Checked on the server: the Teams plugin's storage holds only Fernet ciphertext (no JWT or token field names), separate from Outlook's, and the plugin daemon, API and worker logs contain no tokens.
  • Not tested end to end: the test tenant has no Teams license, so sending channel or chat messages, chats and Planner return Graph's 403 "Failed to get license information" in any mode. This PR does not change those tools' logic, only where their token comes from.
  • Existing OAuth authorization upgraded in place and kept working.

Checklist

  • I have read the contribution guidelines
  • Version bumped in manifest.yaml (0.2.1 → 1.0.0)

@crazywoola
crazywoola deployed to tools/teams November 2, 2026 01:32 — with GitHub Actions Active
@crazywoola
crazywoola merged commit dc2b173 into langgenius:main Nov 2, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
tools/teams — 152f1187 Deployed Nov 2, 2026 by crazywoola via test (tools/teams) #2941
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants