Authenticating with OAuth
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 that can be retrieved for incoming events and used to make scoped API requests.
All of the additional underlying details around authentications can be found within the Slack API documentation!
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
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:
clientId
:string
. An application credential found on the Basic Information page of your app settings.clientSecret
:string
. A secret value also found on the Basic Information page of your app settings.stateSecret
:string
. A secret value used to generate and verify state parameters of authorization requests.scopes
:string[]
. Permissions requested for thebot
user during installation. Explore scopes.installationStore
:InstallationStore
. 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:
Development and testing
Here we've provided a default implementation of the installationStore
with
FileInstallationStore
which can be useful when
developing and testing your app:
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"],
installationStore: new FileInstallationStore(),
});
This is not recommended for use in production - you should implement your own installation store. Please continue reading or inspect our OAuth example apps.
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:
authVersion
:string
. Settings for either new Slack apps (v2
) or "classic" Slack apps (v1
). Most apps usev2
sincev1
was available for a Slack app model that can no longer be created. Default:v2
.directInstall
:boolean
. Skip rendering the installation page atinstallPath
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 tofalse
, 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
. Customized responses to send during OAuth. Default callbacks.stateStore
:StateStore
. Customized generator and validator for OAuth state parameters; the defaultClearStateStore
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
.
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:
const database = {
store: {},
async get(key) {
return this.store[key];
},
async set(key, value) {
this.store[key] = value;
},
};
Completing authentication
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.
Bolt for JavaScript does not support OAuth for
custom receivers. If you're implementing a custom
receiver, you can instead use our @slack/oauth
package, which is
what Bolt for JavaScript uses under the hood.
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 and snippet below:
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
// ...
installerOptions: {
directInstall: true,
installPath: "/slack/installations", // www.example.com/slack/installations
},
});
Add to Slack button
The default 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
and the
generated installation URL:
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
// ...
installerOptions: {
renderHtmlForInstallPath: (addToSlackUrl) => {
return `<a href="${addToSlackUrl}">Add to Slack</a>`;
},
},
});
We do recommend using the provided button generator when formatting links to the authorization page!
Authorization requests with changed or additional scopes require generating a unique authorization URL.
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 URL including your app domain in
app settings under OAuth and Permissions, e.g.
https://example.com/slack/oauth_redirect
.
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:
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"],
redirectUri: "https://example.com/slack/redirect",
installerOptions: {
redirectUriPath: "/slack/redirect",
},
});
Custom callbacks
The page shown after OAuth is complete can be changed with
installerOptions.callbackOptions
to display different details:
const app = new App({
signingSecret: process.env.SLACK_SIGNING_SECRET,
// ...
installerOptions: {
callbackOptions: {
success: (installation, installOptions, req, res) => {
res.send("The installation succeeded!");
},
failure: (error, installOptions, req, res) => {
res.send("Something strange happened...");
},
},
},
});
Full reference reveals these additional options but if no
options are provided, the defaultCallbackSuccess
and defaultCallbackFailure
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 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:
{
team: { id: "T012345678", name: "example-team-name" },
enterprise: undefined,
user: { token: undefined, scopes: undefined, id: "U012345678" },
tokenType: "bot",
isEnterpriseInstall: false,
appId: "A01234567",
authVersion: "v2",
bot: {
scopes: [
"chat:write",
],
token: "xoxb-244493-28*********-********************",
userId: "U001111000",
id: "B01234567"
}
}
The installQuery
object
Bolt also passes an installQuery
object to your fetchInstallation
and
deleteInstallation
handlers:
{
userId: "U012345678",
isEnterpriseInstall: false,
teamId: "T012345678",
enterpriseId: undefined,
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 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:
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
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:
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;
},
};
Additional cases
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. Requesting user scopes during installation is required for these tokens to be issued:
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", "channels:history"],
installerOptions: {
userScopes: ["chat:write"],
},
});
Most OAuth processes remain the same, but the
installation
object received in
storeInstallation
has a user
attribute that should be stored too:
{
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.
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, 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 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:
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: {
stateVerification: false,
},
});
To learn more about the OAuth installation flow with org-wide apps, read the API documentation.
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
:
{
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:
{
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
:
{
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:
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.isEnterpriseInstall &&
installation.enterprise !== undefined
) {
return await database.set(installation.enterprise.id, 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.isEnterpriseInstall &&
installQuery.enterpriseId !== undefined
) {
return await database.get(installQuery.enterpriseId);
}
if (installQuery.teamId !== undefined) {
return await database.get(installQuery.teamId);
}
throw new Error("Failed to fetch installation");
},
deleteInstallation: async (installQuery) => {
if (
installQuery.isEnterpriseInstall &&
installQuery.enterpriseId !== undefined
) {
return await database.delete(installQuery.enterpriseId);
}
if (installQuery.teamId !== undefined) {
return await database.delete(installQuery.teamId);
}
throw new Error("Failed to delete installation");
},
},
});
Example database object
For quick testing purposes, the following might be interesting:
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 out-of-the-box. This
still continues to remain an option using APIs from the
@slack/web-api
package, which aim to make implementing OpenID
Connect (OIDC) connections simple. Alternative routes might be
required.
Explore this relevant package documentation 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:
- Manually instantiate an
ExpressReceiver
instance. - Assign the instance to a variable named
receiver
. - Call the
receiver.installer.generateInstallUrl()
function.
Read more about generateInstallUrl()
in the
"Manually generating installation page 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 for additional details for common error codes.