or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-configuration.mdcontext-and-utilities.mdevent-listeners.mdframework-integration.mdindex.mdoauth-and-installation.md

oauth-and-installation.mddocs/

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