Skip to content

Conversation

@aron-muon
Copy link
Contributor

@aron-muon aron-muon commented Jan 28, 2026

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)

  • 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
  • Fully 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

GovSlack & External OAuth Enhancements

1. Workspace URL Capture and Usage

Problem: External tokens validated successfully via auth.test but subsequent API calls failed with invalid_auth because the Slack client pointed to the wrong API endpoint (default https://slack.com/api/ instead of workspace-specific URLs like GovSlack).

Solution:

  • Capture workspace URL from auth.test response
  • Propagate URL through user context
  • Use slack.OptionAPIURL() when creating Slack clients

2. HTTP-Level Authentication (401 Responses)

Problem: In OAuth mode, authentication errors were only returned as MCP-level errors, not proper HTTP status codes.

Solution:

  • Added OAuthHTTPMiddleware that wraps HTTP handlers with Slack token validation
  • Returns HTTP 401 Unauthorized for:
    • Missing Authorization header
    • Invalid header format (must be Bearer <token>)
    • Invalid Slack tokens (validated via auth.test)
  • OAuth endpoints (/oauth/authorize, /oauth/callback) and /health are excluded from authentication checks
  • Provides clear error messages in the response body

3. 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 #channel and @user prefixes 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 @username display.

6. Slack Mention Text Processing

Problem: User mentions showed concatenated text like U1UEU5QF0Ava Kopp.

Solution: Added regex handlers for Slack mention formats (&lt;@U123&gt;@U123, &lt;@U123|name&gt;@name).

7. User Mention Expansion in Message Text

Problem: conversations_history showed raw user IDs in message text while conversations_search properly expanded to names.

Solution: Added expandUserMentions() function to expand &lt;@U123&gt; to @RealName using the users map.

8. Health Endpoint

Added /health endpoint 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_messages tool 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) and fetchSingleMessage() 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=0 returned all data instead of using default
  • Negative limits were accepted
  • Limits exceeding API maximums caused issues

Solution:

  • limit=0 now uses the default value (50 for history, 100 for search)
  • Negative limits return an error with clear message
  • Search limits capped at 100, channel limits capped at 999

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 cleanRegex to preserve # and @ characters in processed text.

Architecture: OAuth Mode vs Legacy Mode

Caching Differences

Feature Legacy Mode OAuth Mode
User cache Pre-loaded at startup No cache - fetched per request
Channel cache Pre-loaded at startup No cache - fetched per request
API calls Minimal (uses cache) Additional calls for user/channel resolution
Startup time Longer (warming caches) Immediate
Memory usage Higher (cached data) Lower (no persistent cache)

Why no caching in OAuth mode?

In OAuth mode, each request comes from a different user with different permissions. Caching would require:

  • Per-user cache isolation
  • Cache invalidation across users
  • Significantly more memory for multi-tenant scenarios

Instead, OAuth mode makes targeted API calls to fetch the specific information needed:

  • users.info for resolving user IDs to names
  • conversations.list for channel name to ID resolution
  • conversations.info for channel details

This 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 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)

Testing

  • All existing tests pass
  • Manual testing with GovSlack OAuth tokens confirmed working
  • 401 responses verified for missing/invalid tokens
  • Input validation tested for edge cases

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.

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
@aron-muon aron-muon force-pushed the aron/oauth-improvements branch from f35a930 to fdf5913 Compare January 28, 2026 18:22
aron-muon and others added 2 commits January 28, 2026 15:29
- 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.
@aron-muon aron-muon force-pushed the aron/oauth-improvements branch from fb61b6e to 831513f Compare January 28, 2026 18:30
@aron-muon aron-muon changed the title feat: introducing oauth 2.0 multi tenant support feat: OAuth 2.0 multi-user support with GovSlack compatibility Jan 28, 2026
@aron-muon aron-muon marked this pull request as ready for review January 28, 2026 20:25
@aron-muon aron-muon requested a review from korotovsky January 28, 2026 20:25
@aron-muon
Copy link
Contributor Author

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.

@korotovsky
Copy link
Owner

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.

xav-ie and others added 7 commits January 30, 2026 04:34
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).
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.
Flare576 and others added 2 commits January 30, 2026 04:37
…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) {
Copy link
Owner

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?

lswith added a commit to lswith/slack-mcp-server that referenced this pull request Feb 9, 2026
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]>
@lswith
Copy link

lswith commented Feb 9, 2026

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.

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.

6 participants