0
# OAuth & Multi-Workspace Installation
1
2
OAuth 2.0 flow implementation for multi-workspace Slack app installation and management. Includes installation stores, token management, and authorization patterns for enterprise-grade Slack applications.
3
4
## Capabilities
5
6
### OAuth Settings Configuration
7
8
Configure OAuth flow settings for multi-workspace app installation.
9
10
```python { .api }
11
class OAuthSettings:
12
"""OAuth configuration settings for multi-workspace apps."""
13
14
def __init__(
15
self,
16
*,
17
client_id: str,
18
client_secret: str,
19
scopes: Union[str, Sequence[str]],
20
user_scopes: Union[str, Sequence[str]] = None,
21
redirect_uri_path: str = "/slack/oauth_redirect",
22
install_path: str = "/slack/install",
23
success_url: str = None,
24
failure_url: str = None,
25
authorization_url: str = "https://slack.com/oauth/v2/authorize",
26
token_url: str = "https://slack.com/api/oauth.v2.access",
27
state_store=None,
28
installation_store=None,
29
install_page_rendering_enabled: bool = True,
30
redirect_uri_page_rendering_enabled: bool = True
31
):
32
"""
33
Initialize OAuth settings.
34
35
Args:
36
client_id (str): Slack app client ID
37
client_secret (str): Slack app client secret
38
scopes (Union[str, Sequence[str]]): Bot token scopes
39
user_scopes (Union[str, Sequence[str]], optional): User token scopes
40
redirect_uri_path (str): OAuth redirect endpoint path
41
install_path (str): Installation start endpoint path
42
success_url (str, optional): Success redirect URL
43
failure_url (str, optional): Error redirect URL
44
authorization_url (str): Slack authorization endpoint
45
token_url (str): Slack token exchange endpoint
46
state_store: Custom OAuth state store
47
installation_store: Custom installation data store
48
install_page_rendering_enabled (bool): Enable built-in install page
49
redirect_uri_page_rendering_enabled (bool): Enable built-in redirect page
50
"""
51
```
52
53
### OAuth Flow Handler
54
55
Main OAuth flow handler that manages the complete installation process.
56
57
```python { .api }
58
class OAuthFlow:
59
"""OAuth 2.0 flow handler for Slack app installation."""
60
61
def __init__(
62
self,
63
*,
64
settings: OAuthSettings,
65
logger: Logger = None
66
):
67
"""
68
Initialize OAuth flow handler.
69
70
Args:
71
settings (OAuthSettings): OAuth configuration
72
logger (Logger, optional): Custom logger instance
73
"""
74
75
def handle_installation(self, request):
76
"""
77
Handle installation request.
78
79
Args:
80
request: HTTP request object
81
82
Returns:
83
Response: Installation page or redirect response
84
"""
85
86
def handle_callback(self, request):
87
"""
88
Handle OAuth callback.
89
90
Args:
91
request: HTTP request with authorization code
92
93
Returns:
94
Response: Success/failure page or redirect
95
"""
96
```
97
98
### Installation Stores
99
100
Stores for persisting installation data across workspace installations.
101
102
```python { .api }
103
class InstallationStore:
104
"""Base class for installation data persistence."""
105
106
def save(self, installation):
107
"""
108
Save installation data.
109
110
Args:
111
installation: Installation data object
112
"""
113
raise NotImplementedError()
114
115
def find_installation(
116
self,
117
*,
118
enterprise_id: str = None,
119
team_id: str = None,
120
user_id: str = None,
121
is_enterprise_install: bool = None
122
):
123
"""
124
Find installation data.
125
126
Args:
127
enterprise_id (str, optional): Enterprise ID
128
team_id (str, optional): Team/workspace ID
129
user_id (str, optional): User ID
130
is_enterprise_install (bool, optional): Enterprise install flag
131
132
Returns:
133
Installation data or None
134
"""
135
raise NotImplementedError()
136
137
class FileInstallationStore(InstallationStore):
138
"""File-based installation store implementation."""
139
140
def __init__(self, base_dir: str = "./data/installations"):
141
"""
142
Initialize file-based store.
143
144
Args:
145
base_dir (str): Directory for installation data files
146
"""
147
148
class MemoryInstallationStore(InstallationStore):
149
"""In-memory installation store (for development only)."""
150
151
def __init__(self):
152
"""Initialize in-memory store."""
153
```
154
155
### Authorization Results
156
157
Objects representing authorization data for requests.
158
159
```python { .api }
160
class AuthorizeResult:
161
"""Result of authorization process."""
162
163
def __init__(
164
self,
165
*,
166
enterprise_id: str = None,
167
team_id: str = None,
168
user_id: str = None,
169
bot_id: str = None,
170
bot_user_id: str = None,
171
bot_token: str = None,
172
user_token: str = None,
173
bot_scopes: list = None,
174
user_scopes: list = None
175
):
176
"""
177
Initialize authorization result.
178
179
Args:
180
enterprise_id (str, optional): Enterprise Grid organization ID
181
team_id (str, optional): Workspace/team ID
182
user_id (str, optional): User ID
183
bot_id (str, optional): Bot app ID
184
bot_user_id (str, optional): Bot user ID
185
bot_token (str, optional): Bot access token (xoxb-...)
186
user_token (str, optional): User access token (xoxp-...)
187
bot_scopes (list, optional): Bot token scopes
188
user_scopes (list, optional): User token scopes
189
"""
190
```
191
192
### Custom Authorization Functions
193
194
Implement custom authorization logic for advanced use cases.
195
196
```python { .api }
197
def authorize_function(
198
*,
199
enterprise_id: str = None,
200
team_id: str = None,
201
user_id: str = None,
202
logger: Logger
203
) -> AuthorizeResult:
204
"""
205
Custom authorization function signature.
206
207
Args:
208
enterprise_id (str, optional): Enterprise ID from request
209
team_id (str, optional): Team ID from request
210
user_id (str, optional): User ID from request
211
logger (Logger): App logger instance
212
213
Returns:
214
AuthorizeResult: Authorization data or None if not authorized
215
"""
216
```
217
218
## Usage Examples
219
220
### Basic OAuth App Setup
221
222
```python
223
import os
224
from slack_bolt import App
225
from slack_bolt.oauth import OAuthSettings
226
227
# Configure OAuth settings
228
oauth_settings = OAuthSettings(
229
client_id=os.environ["SLACK_CLIENT_ID"],
230
client_secret=os.environ["SLACK_CLIENT_SECRET"],
231
scopes=["chat:write", "commands", "app_mentions:read"],
232
install_path="/slack/install",
233
redirect_uri_path="/slack/oauth_redirect"
234
)
235
236
# Initialize app with OAuth
237
app = App(
238
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
239
oauth_settings=oauth_settings
240
)
241
242
@app.event("app_mention")
243
def handle_mention(event, say):
244
say(f"Thanks for mentioning me, <@{event['user']}>!")
245
246
# OAuth endpoints are automatically handled
247
app.start(port=3000)
248
```
249
250
### Custom Installation Store
251
252
```python
253
import os
254
import json
255
from slack_bolt import App
256
from slack_bolt.oauth import OAuthSettings
257
from slack_bolt.oauth.installation_store import InstallationStore
258
259
class DatabaseInstallationStore(InstallationStore):
260
"""Custom database-backed installation store."""
261
262
def __init__(self, database_url):
263
self.db = connect_to_database(database_url)
264
265
def save(self, installation):
266
"""Save installation to database."""
267
self.db.installations.insert_one({
268
"enterprise_id": installation.enterprise_id,
269
"team_id": installation.team_id,
270
"bot_token": installation.bot_token,
271
"bot_user_id": installation.bot_user_id,
272
"bot_scopes": installation.bot_scopes,
273
"user_id": installation.user_id,
274
"user_token": installation.user_token,
275
"user_scopes": installation.user_scopes,
276
"installed_at": installation.installed_at
277
})
278
279
def find_installation(self, *, enterprise_id=None, team_id=None, user_id=None, is_enterprise_install=None):
280
"""Find installation in database."""
281
query = {}
282
if enterprise_id:
283
query["enterprise_id"] = enterprise_id
284
if team_id:
285
query["team_id"] = team_id
286
if user_id:
287
query["user_id"] = user_id
288
289
result = self.db.installations.find_one(query)
290
if result:
291
return Installation(
292
app_id=result["app_id"],
293
enterprise_id=result.get("enterprise_id"),
294
team_id=result.get("team_id"),
295
bot_token=result.get("bot_token"),
296
bot_user_id=result.get("bot_user_id"),
297
# ... map other fields
298
)
299
return None
300
301
# Use custom installation store
302
oauth_settings = OAuthSettings(
303
client_id=os.environ["SLACK_CLIENT_ID"],
304
client_secret=os.environ["SLACK_CLIENT_SECRET"],
305
scopes=["chat:write", "commands"],
306
)
307
308
app = App(
309
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
310
oauth_settings=oauth_settings,
311
installation_store=DatabaseInstallationStore(os.environ["DATABASE_URL"])
312
)
313
```
314
315
### Custom Authorization Logic
316
317
```python
318
from slack_bolt import App
319
from slack_bolt.authorization import AuthorizeResult
320
321
def custom_authorize(*, enterprise_id, team_id, user_id, logger):
322
"""Custom authorization with business logic."""
323
324
# Check if workspace is allowed
325
if team_id not in ALLOWED_WORKSPACES:
326
logger.warning(f"Unauthorized workspace: {team_id}")
327
return None
328
329
# Get installation data from custom store
330
installation = get_installation_from_db(team_id, user_id)
331
if not installation:
332
logger.warning(f"No installation found for team {team_id}")
333
return None
334
335
# Check subscription status
336
if not check_subscription_active(team_id):
337
logger.warning(f"Inactive subscription for team {team_id}")
338
return None
339
340
# Return authorization result
341
return AuthorizeResult(
342
enterprise_id=enterprise_id,
343
team_id=team_id,
344
user_id=user_id,
345
bot_token=installation["bot_token"],
346
bot_user_id=installation["bot_user_id"],
347
bot_scopes=installation["bot_scopes"]
348
)
349
350
app = App(
351
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
352
authorize=custom_authorize
353
)
354
```
355
356
### User Token Scopes
357
358
```python
359
# Request both bot and user scopes
360
oauth_settings = OAuthSettings(
361
client_id=os.environ["SLACK_CLIENT_ID"],
362
client_secret=os.environ["SLACK_CLIENT_SECRET"],
363
scopes=["chat:write", "commands"], # Bot scopes
364
user_scopes=["search:read", "files:read"] # User scopes
365
)
366
367
app = App(
368
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
369
oauth_settings=oauth_settings
370
)
371
372
@app.command("/search")
373
def search_command(ack, command, client, context):
374
"""Command that uses user token for search."""
375
ack()
376
377
# Use user token from context
378
user_client = WebClient(token=context.user_token)
379
380
try:
381
# Search using user permissions
382
search_result = user_client.search_messages(
383
query=command['text'],
384
count=5
385
)
386
387
messages = search_result["messages"]["matches"]
388
if messages:
389
response = "Search results:\n"
390
for msg in messages[:3]:
391
response += f"• {msg['text'][:100]}...\n"
392
else:
393
response = "No messages found matching your search."
394
395
client.chat_postEphemeral(
396
channel=command['channel_id'],
397
user=command['user_id'],
398
text=response
399
)
400
401
except Exception as e:
402
client.chat_postEphemeral(
403
channel=command['channel_id'],
404
user=command['user_id'],
405
text="Sorry, search failed. Make sure you've granted the necessary permissions."
406
)
407
```
408
409
### Enterprise Grid Support
410
411
```python
412
oauth_settings = OAuthSettings(
413
client_id=os.environ["SLACK_CLIENT_ID"],
414
client_secret=os.environ["SLACK_CLIENT_SECRET"],
415
scopes=["chat:write", "commands"],
416
# Enable org-wide installation for Enterprise Grid
417
install_path="/slack/install",
418
redirect_uri_path="/slack/oauth_redirect"
419
)
420
421
def enterprise_authorize(*, enterprise_id, team_id, user_id, logger):
422
"""Authorization for Enterprise Grid installations."""
423
424
if enterprise_id:
425
# This is an Enterprise Grid installation
426
logger.info(f"Enterprise installation: {enterprise_id}, team: {team_id}")
427
428
# Find org-wide or team-specific installation
429
installation = find_enterprise_installation(enterprise_id, team_id)
430
431
if installation:
432
return AuthorizeResult(
433
enterprise_id=enterprise_id,
434
team_id=team_id,
435
user_id=user_id,
436
bot_token=installation.bot_token,
437
bot_user_id=installation.bot_user_id
438
)
439
440
# Fallback to regular team installation
441
return find_team_installation(team_id, user_id)
442
443
app = App(
444
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
445
oauth_settings=oauth_settings,
446
authorize=enterprise_authorize
447
)
448
```
449
450
### OAuth State Management
451
452
```python
453
from slack_bolt.oauth.oauth_settings import OAuthSettings
454
from slack_bolt.oauth.state_store import FileOAuthStateStore
455
456
# Custom state store for OAuth security
457
state_store = FileOAuthStateStore(
458
expiration_seconds=600, # 10 minutes
459
base_dir="./data/oauth_states"
460
)
461
462
oauth_settings = OAuthSettings(
463
client_id=os.environ["SLACK_CLIENT_ID"],
464
client_secret=os.environ["SLACK_CLIENT_SECRET"],
465
scopes=["chat:write", "commands"],
466
state_store=state_store # Custom state management
467
)
468
469
app = App(
470
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
471
oauth_settings=oauth_settings
472
)
473
```
474
475
### Success and Failure Redirects
476
477
```python
478
oauth_settings = OAuthSettings(
479
client_id=os.environ["SLACK_CLIENT_ID"],
480
client_secret=os.environ["SLACK_CLIENT_SECRET"],
481
scopes=["chat:write", "commands"],
482
success_url="https://yourapp.com/slack/success",
483
failure_url="https://yourapp.com/slack/error",
484
# Disable built-in pages to use custom redirects
485
install_page_rendering_enabled=False,
486
redirect_uri_page_rendering_enabled=False
487
)
488
489
app = App(
490
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
491
oauth_settings=oauth_settings
492
)
493
```
494
495
## Integration Patterns
496
497
### FastAPI Integration with OAuth
498
499
```python
500
from fastapi import FastAPI, Request
501
from slack_bolt import App
502
from slack_bolt.adapter.fastapi import SlackRequestHandler
503
from slack_bolt.oauth import OAuthSettings
504
505
oauth_settings = OAuthSettings(
506
client_id=os.environ["SLACK_CLIENT_ID"],
507
client_secret=os.environ["SLACK_CLIENT_SECRET"],
508
scopes=["chat:write", "commands"]
509
)
510
511
bolt_app = App(
512
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
513
oauth_settings=oauth_settings
514
)
515
516
fastapi_app = FastAPI()
517
handler = SlackRequestHandler(bolt_app)
518
519
# OAuth endpoints
520
@fastapi_app.get("/slack/install")
521
async def install(request: Request):
522
return await handler.handle_async(request)
523
524
@fastapi_app.get("/slack/oauth_redirect")
525
async def oauth_redirect(request: Request):
526
return await handler.handle_async(request)
527
528
# Event endpoint
529
@fastapi_app.post("/slack/events")
530
async def endpoint(request: Request):
531
return await handler.handle_async(request)
532
```
533
534
## Related Topics
535
536
- [App Configuration & Setup](./app-configuration.md) - Basic app initialization and OAuth setup
537
- [Web Framework Integration](./framework-integration.md) - OAuth with FastAPI, Flask, Django
538
- [Context Objects & Utilities](./context-and-utilities.md) - Authorization context and utilities