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({