-
Notifications
You must be signed in to change notification settings - Fork 235
feat: OAuth 2.0 multi-user support with GovSlack compatibility #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Add OAuth 2.0 authentication flow for multi-user support with the following features: Core OAuth Implementation: - OAuth 2.0 authorization flow with Slack API - CSRF protection via state parameter validation - In-memory token storage with automatic cleanup - Per-request Slack client creation for token isolation - Support for both user tokens (xoxp) and bot tokens (xoxb) Dual-Mode Architecture: - Legacy mode: Single-user with browser tokens (xoxc/xoxd/xoxp) - OAuth mode: Multi-user with OAuth tokens - Backward compatible with existing configurations OAuth Handlers: - /oauth/authorize endpoint for initiating OAuth flow - /oauth/callback endpoint for processing OAuth callbacks - OAuth middleware for token validation and user context injection Multi-User Features: - Per-user token isolation and validation - User context propagation through request chain - Separate user and bot token support - Option to post as bot or user via post_as_bot parameter Security Enhancements: - Secure state generation using crypto/rand - Token validation on each request - HTTPS requirement for OAuth callbacks (Slack requirement) - Security headers on OAuth endpoints - Credentials removed from example files Documentation: - Comprehensive OAuth setup guide (docs/04-oauth-setup.md) - ngrok setup instructions for local development - OAuth configuration examples - Architecture and troubleshooting sections Developer Tools: - OAuth testing script (scripts/test-oauth.sh) - OAuth server startup script (start-oauth-server.sh) - Example configuration file (oauth.env.example) Modified Components: - main.go: Added OAuth mode detection and initialization - channels.go: OAuth support with per-request client - conversations.go: OAuth support with user/bot token selection - server.go: OAuth-enabled SSE and HTTP server methods - sse_auth.go: Exported WithAuthKey for OAuth middleware - .gitignore: Added oauth.env and binary exclusions New Components: - pkg/oauth/: OAuth manager, storage, and types - pkg/server/auth/: OAuth middleware and user context - pkg/server/oauth_handler.go: OAuth HTTP handlers Breaking Changes: None (fully backward compatible) Environment Variables: - SLACK_MCP_OAUTH_ENABLED: Enable OAuth mode - SLACK_MCP_OAUTH_CLIENT_ID: Slack app client ID - SLACK_MCP_OAUTH_CLIENT_SECRET: Slack app client secret - SLACK_MCP_OAUTH_REDIRECT_URI: OAuth callback URL (requires HTTPS)
Skip TestIntegrationConversations as it requires: - External Slack workspace with #testcase-1 channel and test data - SLACK_MCP_OPENAI_API environment variable - ngrok forwarding setup This test is from the upstream repo and requires infrastructure not available in CI. Test can be re-enabled when proper test infrastructure is set up.
Remove ability to post as bot for security and clarity: - Removed post_as_bot parameter from conversations handler - Removed bot scopes from OAuth authorization request - Simplified OAuth callback response (no bot token) - Updated documentation to reflect user-only posting Users will now always appear as themselves when posting messages, never as the app bot. This provides better transparency and prevents impersonation concerns.
Add comprehensive OAuth quick-start section to README: - Step-by-step OAuth setup instructions - Comparison with legacy mode (when to use each) - OAuth environment variables added to reference table - Clear benefits and use cases for OAuth vs legacy - MCP client configuration examples Makes it easier for users to understand and choose between OAuth mode (multi-user, production) and legacy mode (single-user, testing).
Merged latest changes from upstream korotovsky/slack-mcp-server: - Bot token (xoxb) support from upstream - Dependency updates and bug fixes - Tool annotations for better LLM understanding - Default cache directory improvements Conflicts resolved: - README.md: Merged OAuth variables with bot token support - Both OAuth and bot token authentication methods now documented Changes from upstream: - xoxb bot token support - Updated dependencies (slack-go v0.17.3) - Cache file location improvements - Various bug fixes and features
f35a930 to
fdf5913
Compare
- Add GovSlack token verification support - Fix OAuth context URL handling and user info retrieval - Update Docker Hub image name and credentials - Fix member count and channel ID lookup issues - Improve conversation and channel handling - Add Trivy security scanning and Dependabot configuration - Update dependencies and Docker entrypoint Co-Authored-By: Claude Opus 4.5 <[email protected]>
Removed skip condition for integration test due to external dependencies.
fb61b6e to
831513f
Compare
|
OK, I've ran a significant amount of manual integration testing, and verified that the changes here work perfectly for handling Oauth for multiple users. |
|
Hi @aron-muon, there was PR #141 merged in master, could you please rebase? After that over a weekend I will try to make a full review. |
Companion to reactions_add (merged in korotovsky#141). Allows removing emoji reactions from messages using the same channel/timestamp/emoji params. Tested with xoxb, xoxc/xoxd token types.
Generalizes error messages since this parser is now shared between reactions_add and reactions_remove. Both tools use the same guardrail (SLACK_MCP_ADD_MESSAGE_TOOL env var).
…TION_TOOL env var
Adds metadata fields to help identify media-containing messages: - BotName: populated from msg.BotProfile.Name for bot messages (e.g., 'giphy') - FileCount: count of attached files - HasMedia: true if message has files OR image blocks This provides visibility into message types that was previously stripped from the Slack API response, addressing user requests in issue korotovsky#88. For SearchMessage results, only HasMedia is populated (via blocks) since the search API doesn't return BotProfile or Files data.
Adds ability to download file content by file ID, addressing maintainer request on PR korotovsky#170. - New files_get tool gated by SLACK_MCP_FILES_TOOL env var - Text files (text/*, application/json, etc.) returned as plain text - Binary files returned as base64-encoded content - 5MB size limit to keep responses reasonable for LLM context - Returns structured JSON: file_id, filename, mimetype, size, encoding, content
Enables agents to discover file IDs from conversation history, completing the workflow for files_get tool usage.
…gy consistency - Tool: files_get → attachment_get_data - Field: FileIDs → AttachmentIDs - Env var: SLACK_MCP_FILES_TOOL → SLACK_MCP_ATTACHMENT_TOOL Per maintainer feedback - aligns with Slack's 'attachment' terminology and may improve LLM performance due to training data prevalence.
| } | ||
|
|
||
| // channelsHandlerOAuth handles channel listing in OAuth mode | ||
| func (ch *ChannelsHandler) channelsHandlerOAuth(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way to avoid defining separate handlers for OAuth?
Add single-user local OAuth flow that starts a temporary HTTPS server with a self-signed cert on localhost:8443, opens the browser for Slack authorization, and exchanges the callback code for an xoxp token. Token resolution in legacy mode now follows: env vars → file store (~/.slack-mcp/token.json) → local OAuth flow, keeping full backward compatibility while enabling interactive CLI login. Also fixes two pre-existing compile errors in PR korotovsky#166's conversations handler (missing parseParamsToolReaction args, duplicate hasMedia decl). Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Hey! I've opened a stacked draft PR on top of this one that adds a local OAuth flow for single-user CLI usage: #195 It adds a self-signed cert HTTPS server on localhost:8443 that handles the browser callback, plus file-based token storage at ~/.slack-mcp/token.json so users dont need to re-auth each time. |
Summary
This PR adds OAuth 2.0 authentication for multi-user support, enabling the Slack MCP server to work with multiple users via OAuth tokens. It includes full GovSlack and Enterprise Grid compatibility.
Based on foundational work by @wentaoyang-moloco (PR #124), with additional enhancements for GovSlack environments and various fixes.
Core OAuth Implementation (from PR #124)
Dual-Mode Architecture
OAuth Handlers
/oauth/authorizeendpoint for initiating OAuth flow/oauth/callbackendpoint for processing OAuth callbacksGovSlack & External OAuth Enhancements
1. Workspace URL Capture and Usage
Problem: External tokens validated successfully via
auth.testbut subsequent API calls failed withinvalid_authbecause the Slack client pointed to the wrong API endpoint (defaulthttps://slack.com/api/instead of workspace-specific URLs like GovSlack).Solution:
auth.testresponseslack.OptionAPIURL()when creating Slack clients2. HTTP-Level Authentication (401 Responses)
Problem: In OAuth mode, authentication errors were only returned as MCP-level errors, not proper HTTP status codes.
Solution:
OAuthHTTPMiddlewarethat wraps HTTP handlers with Slack token validationAuthorizationheaderBearer <token>)auth.test)/oauth/authorize,/oauth/callback) and/healthare excluded from authentication checks3. User Name Resolution in OAuth Mode
Problem: Messages displayed user IDs instead of usernames/display names.
Solution: Added
fetchUsersForMessages()helper to resolve user info from Slack API.4. Channel Name to ID Resolution
Problem: Users couldn't use channel names like
#general- had to know internal channel IDs.Solution: Added
resolveChannelName()helper that handles#channeland@userprefixes with Slack API fallback.5. DM Channel Display Improvements
Problem: DM channels showed
#instead of usernames, member counts were 0.Solution: Detect DM channels and fetch user info for proper
@usernamedisplay.6. Slack Mention Text Processing
Problem: User mentions showed concatenated text like
U1UEU5QF0Ava Kopp.Solution: Added regex handlers for Slack mention formats (
<@U123>→@U123,<@U123|name>→@name).7. User Mention Expansion in Message Text
Problem:
conversations_historyshowed raw user IDs in message text whileconversations_searchproperly expanded to names.Solution: Added
expandUserMentions()function to expand<@U123>to@RealNameusing the users map.8. Health Endpoint
Added
/healthendpoint that returns JSON status and version information. Available in all transport modes (stdio, sse, http) without requiring authentication.Bug Fixes & Input Validation Improvements
1. URL Message Search
Problem: The
conversations_search_messagestool documented URL-based message lookup but it wasn't implemented.Solution: Added
parseSlackMessageURL()to detect and parse Slack message URLs (e.g.,https://slack.com/archives/C1234567890/p1234567890123456) andfetchSingleMessage()to retrieve the specific message directly from conversation history.2. Invalid Cursor Handling
Problem: Invalid pagination cursors were silently ignored, returning results from the beginning instead of an error.
Solution: Updated
paginateChannels()to validate cursor format and return proper errors for malformed cursors (base64 decode failures, invalid channel ID format).3. Limit Validation
Problem: Various edge cases with limit parameter:
limit=0returned all data instead of using defaultSolution:
limit=0now uses the default value (50 for history, 100 for search)4. Search Query Validation
Problem: Empty search queries or missing parameters returned confusing errors.
Solution: Added validation requiring either a search term or at least one filter parameter, with helpful error messages listing available options.
5. Channel Type Validation
Problem: Invalid channel types (e.g.,
channel_types=foo) were silently ignored.Solution: Now returns an error listing valid types:
public_channel,private_channel,im,mpim.6. DM Name Resolution
Problem: Looking up DMs by display name with spaces (e.g.,
@John Smith) failed.Solution: Added case-insensitive matching against username, display name, and real name.
7. User Prefix Validation
Problem: Bare
@without a username was accepted without error.Solution: Now returns an error:
'@' requires a username (e.g., @username).8. Channel/User Mention Preservation
Problem: The text processor regex was stripping
#and@characters from message text, breaking channel mentions like#general.Solution: Updated
cleanRegexto preserve#and@characters in processed text.Architecture: OAuth Mode vs Legacy Mode
Caching Differences
Why no caching in OAuth mode?
In OAuth mode, each request comes from a different user with different permissions. Caching would require:
Instead, OAuth mode makes targeted API calls to fetch the specific information needed:
users.infofor resolving user IDs to namesconversations.listfor channel name to ID resolutionconversations.infofor channel detailsThis trade-off prioritizes correctness and permission isolation over raw performance.
Additional work can be included in future PRs for enabling a cache mode on Oauth - I imagine this would mean adding an intelligent caching mechanism like Redis or Memcached for a global reference of IDs to names.
Environment Variables
SLACK_MCP_OAUTH_ENABLED: Enable OAuth modeSLACK_MCP_OAUTH_CLIENT_ID: Slack app client IDSLACK_MCP_OAUTH_CLIENT_SECRET: Slack app client secretSLACK_MCP_OAUTH_REDIRECT_URI: OAuth callback URL (requires HTTPS)Testing
Breaking Changes
None. All changes are backward compatible.
Credits
Core OAuth implementation by @wentaoyang-moloco. GovSlack compatibility, bug fixes, and additional enhancements by @aron-muon.