diff --git a/docs/content/basic/authenticating-oauth.md b/docs/content/basic/authenticating-oauth.md
index 4b96626fc..949c18571 100644
--- a/docs/content/basic/authenticating-oauth.md
+++ b/docs/content/basic/authenticating-oauth.md
@@ -4,76 +4,274 @@ lang: en
slug: /concepts/authenticating-oauth
---
-To prepare your Slack app for distribution to arbitrary workspaces, you will need to enable Bolt OAuth and store installation information securely. This is because unique access tokens are issued _per app installation_ to specific Slack workspaces. Bolt supports OAuth and will handle the rest of the work; this includes setting up OAuth routes, state verification, and passing your app an installation object which you must store.
+OAuth allows installation of your app to any workspace and is an important step in distributing your app. This is because each app installation issues unique [access tokens with related installation information](#the-installation-object) that can be retrieved for incoming events and used to make scoped API requests.
-To enable OAuth, you must provide:
-* `clientId`, `clientSecret`, `stateSecret` and `scopes` _(required)_
-* An `installationStore` option with handlers that store and fetch installations to and from your database *(optional, strongly recommended in production)*
+All of the additional underlying details around authentications can be found
+within [the Slack API documentation][oauth-v2]!
----
+## Configuring the application
+
+To set your Slack app up for distribution, you will need to enable Bolt OAuth
+and store installation information securely. Bolt supports OAuth by using the
+[`@slack/oauth`][oauth-node] package to handle most of the work; this includes
+setting up OAuth routes, verifying state, and passing your app an installation
+object which you must store.
+
+### App options
+
+The following `App` options are required for OAuth installations:
-## Example OAuth Bolt Apps
+- `clientId`: `string`. An application credential found on the **Basic
+ Information** page of your [app settings][app-settings].
+- `clientSecret`: `string`. A secret value also found on the **Basic
+ Information** page of your [app settings][app-settings].
+- `stateSecret`: `string`. A secret value used to
+ [generate and verify state][verification] parameters of authorization
+ requests.
+- `scopes`: `string[]`. Permissions requested for the `bot` user during
+ installation. [Explore scopes][scopes].
+- `installationStore`: [`InstallationStore`][installation-store]. Handlers that
+ store, fetch, and delete installation information to and from your database.
+ Optional, but strongly recommended in production.
+
+### Example OAuth Bolt Apps
Check out the following examples in the bolt-js project for code samples:
- [Bolt OAuth app using the classic HTTP Receiver](https://github.com/slackapi/bolt-js/tree/main/examples/oauth)
- [Bolt OAuth app using the Express Receiver](https://github.com/slackapi/bolt-js/tree/main/examples/oauth-express-receiver)
----
-
-## Development and Testing
+#### Development and testing
-We've provided a default implementation of the `installationStore` option — `FileInstallationStore` — which you can use during app development and testing.
+Here we've provided a default implementation of the `installationStore` with
+[`FileInstallationStore`][installation-store-file] which can be useful when
+developing and testing your app:
```javascript
-const { App } = require('@slack/bolt');
-const { FileInstallationStore } = require('@slack/oauth');
+const { App } = require("@slack/bolt");
+const { FileInstallationStore } = require("@slack/oauth");
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
stateSecret: process.env.SLACK_STATE_SECRET,
- scopes: ['channels:history', 'chat:write', 'commands'],
+ scopes: ["channels:history", "chat:write", "commands"],
installationStore: new FileInstallationStore(),
});
```
-:::warning
-This is **_not_** recommended for use in production - you should implement your own production store. Please see the example code to the right and [our other OAuth examples](https://github.com/slackapi/bolt-js/tree/main/examples/oauth).
+:::warning
+
+This is **not** recommended for use in production - you should
+[implement your own installation store](#installation-store). Please continue
+reading or inspect [our OAuth example apps][examples].
:::
+### Installer options
+
+We provide several options for customizing default OAuth using the
+`installerOptions` object, which can be passed in during the initialization of
+`App`. You can override these common options and
+[find others here][install-provider-options]:
+
+- `authVersion`: `string`. Settings for either new Slack apps (`v2`) or
+ "classic" Slack apps (`v1`). Most apps use `v2` since `v1` was available for a
+ Slack app model that can no longer be created. Default: `v2`.
+- `directInstall`: `boolean`. Skip rendering the
+ [installation page](#add-to-slack-button) at `installPath` and redirect to the
+ authorization URL instead. Default: `false`.
+- `installPath`: `string`. Path of the URL for starting an installation.
+ Default: `/slack/install`.
+- `metadata`: `string`. Static information shared between requests as install
+ URL options. Optional.
+- `redirectUriPath`: `string`. Path of the installation callback URL. Default:
+ `/slack/oauth_redirect`.
+- `stateVerification`: `boolean`. Option to customize the state verification
+ logic. When set to `false`, the app does not verify the state parameter. While
+ not recommended for general OAuth security, some apps might want to skip this
+ for internal installations within an enterprise grid org. Default: `true`.
+- `userScopes`: `string[]`. User scopes to request during installation. Default:
+ `[]`.
+- `callbackOptions`: [`CallbackOptions`][callback-options]. Customized
+ [responses to send][callbacks] during OAuth.
+ [Default callbacks][callback-options-default].
+- `stateStore`: [`StateStore`][state-store]. Customized generator and validator
+ for [OAuth state parameters][state]; the default `ClearStateStore` should work
+ well for most scenarios. However, if you need even better security, storing
+ state parameter data with a server-side database would be a good approach.
+ Default: [`ClearStateStore`][state-store-clear].
+
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ clientId: process.env.SLACK_CLIENT_ID,
+ clientSecret: process.env.SLACK_CLIENT_SECRET,
+ scopes: [
+ "channels:manage",
+ "channels:read",
+ "chat:write",
+ "groups:read",
+ "incoming-webhook",
+ ],
+ installerOptions: {
+ authVersion: "v2",
+ directInstall: false,
+ installPath: "/slack/install",
+ metadata: "",
+ redirectUriPath: "/slack/oauth_redirect",
+ stateVerification: "true",
+ /**
+ * Example user scopes to request during installation.
+ */
+ userScopes: ["chat:write"],
+ /**
+ * Example pages to navigate to on certain callbacks.
+ */
+ callbackOptions: {
+ success: (installation, installUrlOptions, req, res) => {
+ res.send("The installation succeeded!");
+ },
+ failure: (error, installUrlOptions, req, res) => {
+ res.send("Something strange happened...");
+ },
+ },
+ /**
+ * Example validation of installation options using a random state and an
+ * expiration time between requests.
+ */
+ stateStore: {
+ generateStateParam: async (installUrlOptions, now) => {
+ const state = randomStringGenerator();
+ const value = { options: installUrlOptions, now: now.toJSON() };
+ await database.set(state, value);
+ return state;
+ },
+ verifyStateParam: async (now, state) => {
+ const value = await database.get(state);
+ const generated = new Date(value.now);
+ const seconds = Math.floor(
+ (now.getTime() - generated.getTime()) / 1000,
+ );
+ if (seconds > 600) {
+ throw new Error("The state expired after 10 minutes!");
+ }
+ return value.options;
+ },
+ },
+ },
+});
+```
+
+
+ Example database object
+
+For quick testing purposes, the following might be interesting:
+
+```javascript
+const database = {
+ store: {},
+ async get(key) {
+ return this.store[key];
+ },
+ async set(key, value) {
+ this.store[key] = value;
+ },
+};
+```
+
+
+
---
-## Installing your App
+## Completing authentication
-* **Initiating an installation**: Bolt for JavaScript provides an **Install Path** at the `/slack/install` URL out-of-the-box. This endpoint returns a simple page with an `Add to Slack` button which initiates a direct install of your app (with a valid `state` parameter). For example, an app hosted at _www.example.com_ would serve the install page at _www.example.com/slack/install_.
+The complete authentication handshake involves requesting scopes using a
+generated installation URL and processing approved installations. Bolt handles
+this with a default installation and callback route, but some configurations to
+the app settings are needed and changes to these routes might be desired.
-:::tip
+:::info
-You can skip rendering the provided default webpage and navigate users directly to the Slack authorization URL by setting `installerOptions.directInstall: true` in the `App` constructor ([example](https://github.com/slackapi/bolt-js/blob/5b4d9ceb65e6bf5cf29dfa58268ea248e5466bfb/examples/oauth/app.js#L58-L64)).
+Bolt for JavaScript does not support OAuth for
+[custom receivers](/concepts/receiver). If you're implementing a custom
+receiver, you can instead use our [`@slack/oauth`][oauth-node] package, which is
+what Bolt for JavaScript uses under the hood.
:::
-* **Add to Slack**: The `Add to Slack` button initiates the OAuth process with Slack. After users have clicked Allow to grant your app permissions, Slack will call your app's **Redirect URI** (provided out-of-the-box), and prompt users to **Open Slack**. See the **Redirect URI** section below for customization options.
+### Installing your App
+
+Bolt for JavaScript provides an **Install Path** at the `/slack/install` URL
+out-of-the-box. This endpoint returns a simple static page that includes an
+`Add to Slack` button that links to a generated authorization URL for your app.
+This has the right scopes, a valid `state`, the works.
+
+For example, an app hosted at _www.example.com_ will serve the install page at
+_www.example.com/slack/install_ but this path can be changed with
+`installerOptions.installPath`. Rendering a webpage before the authorization URL
+is also optional and can be skipped using `installerOptions.directInstall`.
+
+Inspect this [example app][direct-install] and snippet below:
+
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ // ...
+ installerOptions: {
+ // highlight-start
+ directInstall: true,
+ installPath: "/slack/installations", // www.example.com/slack/installations
+ // highlight-end
+ },
+});
+```
+
+#### Add to Slack button
+
+The [default][add-to-slack] `Add to Slack` button initiates the OAuth process
+with Slack using a generated installation URL. If customizations are wanted to
+this page, changes can be made using
+[`installerOptions.renderHtmlForInstallPath`][installation-page] and the
+generated installation URL:
-* **Open Slack**: After users **Open Slack**, as your app processes events from Slack, your provided `installationStore`'s `fetchInstallation` and `storeInstallation` handlers will execute to store and retrieve workspace-specific access tokens as needed. See the **Installation Object** section below for more detail on arguments passed to those handlers.
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ // ...
+ installerOptions: {
+ // highlight-start
+ renderHtmlForInstallPath: (addToSlackUrl) => {
+ return `Add to Slack`;
+ },
+ // highlight-end
+ },
+});
+```
-* If you need additional authorizations/permissions (such as user scopes and tokens) from users inside a team when your app is already installed, or have a reason to dynamically generate an install URL, manually instantiate an `ExpressReceiver`, assign the instance to a variable named `receiver`, and then call `receiver.installer.generateInstallUrl()`. Read more about `generateInstallUrl()` in the ["Manually generating installation page URL" section of the OAuth docs](https://slack.dev/node-slack-sdk/oauth#using-handleinstallpath).
+We do recommend using the provided [button generator][oauth-v2] when formatting
+links to the authorization page!
-:::info
+:::note
-Bolt for JavaScript does not support OAuth for [custom receivers](/concepts/receiver). If you're implementing a custom receiver, you can use our [Slack OAuth library](https://slack.dev/node-slack-sdk/oauth), which is what Bolt for JavaScript uses under the hood.
+Authorization requests with changed or additional scopes require
+[generating a unique authorization URL](#extra-authorizations).
:::
----
-## Redirect URI
-Bolt for JavaScript provides a **Redirect URI Path** `/slack/oauth_redirect`. Slack uses the Redirect URI to redirect users after they complete an app's installation flow.
+### Redirect URL
+
+Bolt for JavaScript provides the **Redirect URL** path `/slack/oauth_redirect`
+out-of-the-box for Slack to use when redirecting users that complete the OAuth
+installation flow.
-You will need to add the full **Redirect URI** including your app domain in your Slack app configuration settings under **OAuth and Permissions**, e.g. `https://example.com/slack/oauth_redirect`.
+You will need to add the full **Redirect URL** including your app domain in
+[app settings][settings] under **OAuth and Permissions**, e.g.
+`https://example.com/slack/oauth_redirect`.
-To supply your own custom **Redirect URI**, you can set `redirectUri` in the App options and `installerOptions.redirectUriPath`. You must supply both, and the path must be consistent with the full URI.
+To supply a custom Redirect URL, you can set `redirectUri` in the App options
+and `installerOptions.redirectUriPath`. Both must be supplied and be consistent
+with the full URL if a custom Redirect URL is provided:
```javascript
const app = new App({
@@ -81,188 +279,453 @@ const app = new App({
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
stateSecret: process.env.SLACK_STATE_SECRET,
- scopes: ['chat:write'],
- redirectUri: 'https://example.com/slack/redirect', // here
+ scopes: ["chat:write"],
+ // highlight-next-line
+ redirectUri: "https://example.com/slack/redirect",
installerOptions: {
- redirectUriPath: '/slack/redirect', // and here!
+ // highlight-next-line
+ redirectUriPath: "/slack/redirect",
},
});
```
----
+#### Custom callbacks
-## Installation object
-Bolt will pass your `installationStore`'s `storeInstallation` handler an `installation`. The `installation` object has the following shape:
+The page shown after OAuth is complete can be changed with
+`installerOptions.callbackOptions` to display different details:
+
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ // ...
+ installerOptions: {
+ // highlight-start
+ callbackOptions: {
+ success: (installation, installOptions, req, res) => {
+ res.send("The installation succeeded!");
+ },
+ failure: (error, installOptions, req, res) => {
+ res.send("Something strange happened...");
+ },
+ },
+ // highlight-end
+ },
+});
+```
+
+Full reference reveals [these additional options][callback-options] but if no
+options are provided, the [`defaultCallbackSuccess`][callback-default-success]
+and [`defaultCallbackFailure`][callback-default-failure] callbacks are used.
+
+### Workspace installations
+
+Incoming installations are received after a successful OAuth process and must be
+stored for later lookup. This happens in the terms of installation objects and
+an installation store.
+
+The following outlines installations to individual workspaces with more
+[information on org-wide installations](#org-wide-installation) below.
+
+#### Installation objects
+
+##### The `installation` object
+
+Bolt passes an `installation` object to the `storeInstallation` method of your
+`installationStore` after each installation. When installing the app to a single
+workspace team, the `installation` object has the following shape:
```javascript
{
- team: { id: 'T012345678', name: 'example-team-name' },
+ team: { id: "T012345678", name: "example-team-name" },
enterprise: undefined,
- user: { token: undefined, scopes: undefined, id: 'U01234567' },
- tokenType: 'bot',
+ user: { token: undefined, scopes: undefined, id: "U012345678" },
+ tokenType: "bot",
isEnterpriseInstall: false,
- appId: 'A01234567',
- authVersion: 'v2',
+ appId: "A01234567",
+ authVersion: "v2",
bot: {
scopes: [
- 'chat:write',
+ "chat:write",
],
- token: 'xoxb-244493-28*********-********************',
- userId: 'U012345678',
- id: 'B01234567'
+ token: "xoxb-244493-28*********-********************",
+ userId: "U001111000",
+ id: "B01234567"
}
}
```
-Bolt will pass your `fetchInstallation` and `deleteInstallation` handlers an `installQuery` object:
+
+##### The `installQuery` object
+
+Bolt also passes an `installQuery` object to your `fetchInstallation` and
+`deleteInstallation` handlers:
```javascript
{
- userId: 'U012345678',
+ userId: "U012345678",
isEnterpriseInstall: false,
- teamId: 'T012345678',
+ teamId: "T012345678",
enterpriseId: undefined,
- conversationId: 'D02345678'
+ conversationId: "D02345678"
}
```
+#### Installation store
+
+The `installation` object received above must be stored after installations for
+retrieval during lookup or removal during deletion using values from the
+`installQuery` object.
+
+An [installation store][store] implements the handlers `storeInstallation`,
+`fetchInstallation`, and `deleteInstallation` for each part of this process. The
+following implements a simple installation store in memory, but persistent
+storage is strongly recommended for production:
+
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ clientId: process.env.SLACK_CLIENT_ID,
+ clientSecret: process.env.SLACK_CLIENT_SECRET,
+ stateSecret: process.env.SLACK_STATE_SECRET,
+ scopes: ["chat:write", "commands"],
+ installationStore: {
+ storeInstallation: async (installation) => {
+ if (installation.team !== undefined) {
+ return await database.set(installation.team.id, installation);
+ }
+ throw new Error("Failed to save installation data to installationStore");
+ },
+ fetchInstallation: async (installQuery) => {
+ if (installQuery.teamId !== undefined) {
+ return await database.get(installQuery.teamId);
+ }
+ throw new Error("Failed to fetch installation");
+ },
+ deleteInstallation: async (installQuery) => {
+ if (installQuery.teamId !== undefined) {
+ return await database.delete(installQuery.teamId);
+ }
+ throw new Error("Failed to delete installation");
+ },
+ },
+});
+```
+
+Lookups for the `fetchInstallation` handler happen as part of the built-in
+[`authorization`][authorization] of incoming events and provides app listeners
+with the `context.botToken` object for convenient use.
+
+
+ Example database object
+
+For quick testing purposes, the following might be interesting:
+
+```javascript
+const database = {
+ store: {},
+ async delete(key) {
+ delete this.store[key];
+ },
+ async get(key) {
+ return this.store[key];
+ },
+ async set(key, value) {
+ this.store[key] = value;
+ },
+};
+```
+
+
+
---
-## Org-wide installation
-To add support for [org-wide installations](https://api.slack.com/enterprise/apps), you will need Bolt for JavaScript version `3.0.0` or later. Make sure you have enabled org-wide installation in your app configuration settings under **Org Level Apps**.
-Installing an [org-wide](https://api.slack.com/enterprise/apps) app from admin pages requires additional configuration to work with Bolt. In that scenario, the recommended `state` parameter is not supplied. Bolt will try to verify `state` and stop the installation from progressing.
+## Additional cases
-You may disable state verification in Bolt by setting the `stateVerification` option to false. See the example setup below:
+The above sections set your app up for collecting a bot token on workspace
+installations with handfuls of configuration, but other cases might still be
+explored.
+
+### User tokens
+
+User tokens represent workspace members and can be used to
+[take action on behalf of users][user-tokens]. Requesting user scopes during
+installation is required for these tokens to be issued:
```javascript
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
- scopes: ['chat:write'],
+ scopes: ["chat:write", "channels:history"],
installerOptions: {
+ userScopes: ["chat:write"],
+ },
+});
+```
+
+Most OAuth processes remain the same, but the
+[`installation`](#the-installation-object) object received in
+`storeInstallation` has a `user` attribute that should be stored too:
+
+```javascript
+{
+ team: { id: "T012345678", name: "example-team-name" },
+ user: {
+ token: "xoxp-314159-26*********-********************",
+ scopes: ["chat:write"],
+ id: "U012345678"
+ },
+ tokenType: "bot",
+ appId: "A01234567",
+ // ...
+}
+```
+
+Successful `fetchInstallation` lookups will also include the `context.userToken`
+object associated with the received event in the app listener arguments.
+
+:::note
+
+The `tokenType` value remains `"bot"` while `scopes` are requested, even with
+the included `userScopes`. This suggests `bot` details exist, and is `undefined`
+along with the `bot` if no bot `scopes` are requested.
+
+:::
+
+### Org-wide installations
+
+To add support for [org-wide installations][org-ready], you will need Bolt for
+JavaScript version `3.0.0` or later. Make sure you have enabled org-wide
+installation in your app configuration settings under **Org Level Apps**.
+
+#### Admin installation state verficiation
+
+Installing an [org-wide](https://api.slack.com/enterprise/apps) app from admin
+pages requires additional configuration to work with Bolt. In that scenario, the
+recommended `state` parameter is not supplied. Bolt will try to verify `state`
+and stop the installation from progressing.
+
+You may disable state verification in Bolt by setting the `stateVerification`
+option to false. See the example setup below:
+
+```javascript
+const app = new App({
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
+ clientId: process.env.SLACK_CLIENT_ID,
+ clientSecret: process.env.SLACK_CLIENT_SECRET,
+ scopes: ["chat:write"],
+ installerOptions: {
+ // highlight-next-line
stateVerification: false,
},
});
```
-To learn more about the OAuth installation flow with Slack, [read the API documentation](https://api.slack.com/authentication/oauth-v2).
+To learn more about the OAuth installation flow with org-wide apps,
+[read the API documentation][org-ready-oauth].
+
+#### Org-wide installation objects
+
+Being installed to an organization can grant your app access to multiple
+workspaces and the associated events.
+
+##### The org-wide `installation` object
+
+The `installation` object from installations to a team in an organization have
+an additional `enterprise` object and `isEnterpriseInstall` set to either `true`
+or `false`:
```javascript
-const database = {
- async get(key) {},
- async delete(key) {},
- async set(key, value) {}
-};
+{
+ team: undefined,
+ enterprise: { id: "E0000000001", name: "laboratories" },
+ user: { token: undefined, scopes: undefined, id: "U0000000001" },
+ tokenType: "bot",
+ isEnterpriseInstall: true,
+ appId: "A0000000001",
+ authVersion: "v2",
+ bot: {
+ scopes: [
+ "chat:write",
+ ],
+ token: "xoxb-000001-00*********-********************",
+ userId: "U0000000002",
+ id: "B0000000001"
+ }
+}
+```
+
+Apps installed org-wide will receive the `isEnterpriseInstall` parameter as
+`true`, but apps could also still be installed to individual workspaces in
+organizations. These apps receive installation information for both the `team`
+and `enterprise` parameters:
+
+```javascript
+{
+ team: { id: "T0000000001", name: "experimental-sandbox" },
+ enterprise: { id: "E0000000001", name: "laboratories" },
+ // ...
+ isEnterpriseInstall: false,
+ // ...
+}
+```
+
+##### The org-wide `installQuery` object
+
+This `installQuery` object provided to the `fetchInstallation` and
+`deleteInstallation` handlers is the same as ever, but now with an additional
+value, `enterpriseId`, defined and another possible `true` or `false` value for
+`isEnterpriseInstall`:
+
+```javascript
+{
+ userId: "U0000000001",
+ isEnterpriseInstall: true,
+ teamId: "T0000000001",
+ enterpriseId: "E0000000001",
+ conversationId: "D0000000001"
+}
+```
+
+#### Org-wide installation store
+Storing and retrieving installations from an installation store requires similar
+handling as before, but with additional checks for org-wide installations of
+org-ready apps:
+
+```javascript
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
stateSecret: process.env.SLACK_STATE_SECRET,
- scopes: ['chat:write', 'commands'],
+ scopes: ["chat:write", "commands"],
+ // highlight-start
installationStore: {
storeInstallation: async (installation) => {
- // Bolt will pass your handler an installation object
- // Change the lines below so they save to your database
- if (installation.isEnterpriseInstall && installation.enterprise !== undefined) {
- // handle storing org-wide app installation
+ if (
+ installation.isEnterpriseInstall &&
+ installation.enterprise !== undefined
+ ) {
return await database.set(installation.enterprise.id, installation);
}
if (installation.team !== undefined) {
- // single team app installation
return await database.set(installation.team.id, installation);
}
- throw new Error('Failed saving installation data to installationStore');
+ throw new Error("Failed to save installation data to installationStore");
},
fetchInstallation: async (installQuery) => {
- // Bolt will pass your handler an installQuery object
- // Change the lines below so they fetch from your database
- if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
- // handle org wide app installation lookup
+ if (
+ installQuery.isEnterpriseInstall &&
+ installQuery.enterpriseId !== undefined
+ ) {
return await database.get(installQuery.enterpriseId);
}
if (installQuery.teamId !== undefined) {
- // single team app installation lookup
return await database.get(installQuery.teamId);
}
- throw new Error('Failed fetching installation');
+ throw new Error("Failed to fetch installation");
},
deleteInstallation: async (installQuery) => {
- // Bolt will pass your handler an installQuery object
- // Change the lines below so they delete from your database
- if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
- // org wide app installation deletion
+ if (
+ installQuery.isEnterpriseInstall &&
+ installQuery.enterpriseId !== undefined
+ ) {
return await database.delete(installQuery.enterpriseId);
}
if (installQuery.teamId !== undefined) {
- // single team app installation deletion
return await database.delete(installQuery.teamId);
}
- throw new Error('Failed to delete installation');
+ throw new Error("Failed to delete installation");
},
},
+ // highlight-end
});
```
-
-Customizing OAuth defaults
-
-
-We provide several options for customizing default OAuth using the `installerOptions` object, which can be passed in during the initialization of `App`. You can override the following:
+ Example database object
-- `authVersion`: Used to toggle between new Slack Apps and Classic Slack Apps
-- `metadata`: Used to pass around session related information
-- `installPath`: Override default path for "Add to Slack" button
-- `redirectUriPath`: This relative path must match the `redirectUri` provided in the App options
-- `callbackOptions`: Provide custom success and failure pages at the end of the OAuth flow
-- `stateStore`: Provide a custom state store instead of using the built in `ClearStateStore`
-- `userScopes`: Array of user scopes needed when the user installs the app, similar to `scopes` attribute at the parent level.
+For quick testing purposes, the following might be interesting:
```javascript
-const app = new App({
- signingSecret: process.env.SLACK_SIGNING_SECRET,
- clientId: process.env.SLACK_CLIENT_ID,
- clientSecret: process.env.SLACK_CLIENT_SECRET,
- scopes: ['channels:read', 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
- installerOptions: {
- authVersion: 'v1', // default is 'v2', 'v1' is used for classic slack apps
- metadata: 'some session data',
- installPath: '/slack/installApp',
- redirectUriPath: '/slack/redirect',
- userScopes: ['chat:write'],
- callbackOptions: {
- success: (installation, installOptions, req, res) => {
- // Do custom success logic here
- res.send('successful!');
- },
- failure: (error, installOptions , req, res) => {
- // Do custom failure logic here
- res.send('failure');
- }
- },
- stateStore: {
- // Do not need to provide a `stateSecret` when passing in a stateStore
- // generateStateParam's first argument is the entire InstallUrlOptions object which was passed into generateInstallUrl method
- // the second argument is a date object
- // the method is expected to return a string representing the state
- generateStateParam: async (installUrlOptions, date) => {
- // generate a random string to use as state in the URL
- const randomState = randomStringGenerator();
- // save installOptions to cache/db
- await myDB.set(randomState, installUrlOptions);
- // return a state string that references saved options in DB
- return randomState;
- },
- // verifyStateParam's first argument is a date object and the second argument is a string representing the state
- // verifyStateParam is expected to return an object representing installUrlOptions
- verifyStateParam: async (date, state) => {
- // fetch saved installOptions from DB using state reference
- const installUrlOptions = await myDB.get(randomState);
- return installUrlOptions;
- }
- },
- }
-});
+const database = {
+ store: {},
+ async get(key) {
+ return this.store[key];
+ },
+ async delete(key) {
+ delete this.store[key];
+ },
+ async set(key, value) {
+ this.store[key] = value;
+ },
+};
```
+
+
+### Sign in with Slack
+
+Right now Bolt does not support [Sign in with Slack][siws] out-of-the-box. This
+still continues to remain an option using APIs from the
+[`@slack/web-api`][web-api] package, which aim to make implementing OpenID
+Connect (OIDC) connections simple. Alternative [routes][custom-routes] might be
+required.
+
+Explore [this relevant package documentation][oidc] for reference and example.
+
+### Extra authorizations
+
+If you need additional authorizations or permissions, such as user scopes for
+user tokens from users of a team where your app is already installed, or have a
+reason to dynamically generate an install URL, an additional installation is
+required.
+
+Generating a new installation URL requires a few steps:
+
+1. Manually instantiate an `ExpressReceiver` instance.
+2. Assign the instance to a variable named `receiver`.
+3. Call the `receiver.installer.generateInstallUrl()` function.
+
+Read more about `generateInstallUrl()` in the
+["Manually generating installation page URL"][generate-install-url] section of
+the `@slack/oauth` docs.
+
+### Common errors
+
+Occasional mishaps in various places throughout the OAuth process can cause
+errors, but these often have meaning! Explore [the API documentation][errors]
+for additional details for common error codes.
+
+[add-to-slack]: https://github.com/slackapi/node-slack-sdk/blob/main/packages/oauth/src/default-render-html-for-install-path.ts
+[app-settings]: https://api.slack.com/apps
+[authorization]: /concepts/authorization
+[callback-default-failure]: https://github.com/slackapi/node-slack-sdk/blob/e5a4f3fbbd4f6aad9fdd415976f80668b01fd442/packages/oauth/src/callback-options.ts#L127-L162
+[callback-default-success]: https://github.com/slackapi/node-slack-sdk/blob/e5a4f3fbbd4f6aad9fdd415976f80668b01fd442/packages/oauth/src/callback-options.ts#L81-L125
+[callback-options]: https://slack.dev/node-slack-sdk/reference/oauth/interfaces/CallbackOptions
+[callback-options-default]: https://github.com/slackapi/node-slack-sdk/blob/e5a4f3fbbd4f6aad9fdd415976f80668b01fd442/packages/oauth/src/callback-options.ts#L81-L162
+[callbacks]: https://slack.dev/node-slack-sdk/reference/oauth/interfaces/CallbackOptions
+[custom-routes]: /concepts/custom-routes
+[direct-install]: https://github.com/slackapi/bolt-js/blob/5b4d9ceb65e6bf5cf29dfa58268ea248e5466bfb/examples/oauth/app.js#L58-L64
+[errors]: https://api.slack.com/authentication/oauth-v2#errors
+[examples]: https://github.com/slackapi/bolt-js/tree/main/examples/oauth
+[generate-install-url]: https://slack.dev/node-slack-sdk/oauth/#using-handleinstallpath
+[install-provider-options]: https://github.com/slackapi/node-slack-sdk/blob/main/packages/oauth/src/install-provider-options.ts
+[installation-page]: https://slack.dev/node-slack-sdk/oauth/#showing-an-installation-page
+[installation-store]: https://slack.dev/node-slack-sdk/reference/oauth/interfaces/InstallationStore
+[installation-store-file]: https://github.com/slackapi/node-slack-sdk/blob/main/packages/oauth/src/installation-stores/file-store.ts
+[oauth-node]: https://slack.dev/node-slack-sdk/oauth
+[oauth-v2]: https://api.slack.com/authentication/oauth-v2
+[oidc]: https://slack.dev/node-slack-sdk/web-api#sign-in-with-slack-via-openid-connect
+[org-ready]: https://api.slack.com/enterprise/org-ready-apps
+[org-ready-oauth]: https://api.slack.com/enterprise/org-ready-apps#oauth
+[scopes]: https://api.slack.com/scopes
+[settings]: https://api.slack.com/apps
+[siws]: https://api.slack.com/authentication/sign-in-with-slack
+[state]: https://slack.dev/node-slack-sdk/oauth#using-a-custom-state-store
+[state-store]: https://slack.dev/node-slack-sdk/reference/oauth/interfaces/StateStore
+[state-store-clear]: https://github.com/slackapi/node-slack-sdk/blob/main/packages/oauth/src/state-stores/clear-state-store.ts
+[store]: https://slack.dev/node-slack-sdk/oauth#storing-installations-in-a-database
+[user-tokens]: https://api.slack.com/concepts/token-types#user
+[verification]: https://slack.dev/node-slack-sdk/oauth#state-verification
+[web-api]: https://slack.dev/node-slack-sdk/web-api
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index ba657ff5b..45378d07b 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -25,6 +25,7 @@
--slack-dark-blue: #1264a3;
--grey: #868686;
+ --dim: #eef2f6;
--white: #ffffff;
}
@@ -44,6 +45,7 @@ p a {
/* adjusting for light and dark modes */
[data-theme="light"] {
+ --docusaurus-highlighted-code-line-bg: var(--dim);
--ifm-color-primary: var(--aubergine-active);
--ifm-footer-background-color: var(--horchata);
--slack-link: var(--slack-dark-blue);
diff --git a/examples/oauth/app.js b/examples/oauth/app.js
index ab9ef75ca..289d09695 100644
--- a/examples/oauth/app.js
+++ b/examples/oauth/app.js
@@ -3,11 +3,14 @@ const { App, LogLevel } = require('@slack/bolt');
const databaseData = {};
const database = {
set: async (key, data) => {
- databaseData[key] = data
+ databaseData[key] = data;
},
get: async (key) => {
return databaseData[key];
},
+ delete: async (key) => {
+ delete databaseData[key];
+ },
};
const app = new App({