Skip to main content

Integrating with services requiring external authentication

Workflow apps require a paid plan

Join the Developer Program and provision a sandbox with access to all Slack features for free.

You can use the Slack CLI to encrypt and to store OAuth2 credentials. This enables your app to access information from another service without exchanging passwords, but rather, tokens.

What is OAuth2, and why should you use it?

OAuth2 stands for Open Authorization 2.0. It is a standard protocol designed to allow apps to access resources hosted by other apps on behalf of a user. Unlike basic authorization, where you share a password with a user, OAuth2 uses access tokens to verify a user's identity. For providers that require it, Slack offers PKCE and HTTP Basic Auth support, as you will see in the OAuth2 provider options properties section below.

The following steps guide you through integrating your app with an external service using Google as an example.

1. Obtain your OAuth2 credentials

The first step is to obtain your OAuth2 credentials. To do that, create a new OAuth2 credential with the external service you'll be integrating with.

For our example, navigate to the Google API Console to obtain your OAuth2 client ID and client secret.

If you're asked to provide a redirect URL, use the following:

https://oauth2.slack.com/external/auth/callback

When you're done creating your credential, copy the credential's client ID and client secret, then head to your manifest file (manifest.ts).

2. Define your OAuth2 provider

Next, tell your app about your OAuth2 provider by defining an OAuth2 provider within your app. Inside your app's manifest, import DefineOAuth2Provider. Then, create a new provider instance.

An example provider instance for Google is below. You can define it right in your manifest, or put it in its own file and import it into the manifest.

// manifest.ts
import { DefineFunction, DefineOAuth2Provider, DefineWorkflow, Manifest, Schema,} from "deno-slack-sdk/mod.ts";
// ...

// Define a new OAuth2 provider
// Note: replace <your_client_id> with your actual client ID
// if you're following along and creating an OAuth2 provider for
// Google.
const GoogleProvider = DefineOAuth2Provider({
provider_key: "google",
provider_type: Schema.providers.oauth2.CUSTOM,
options: {
provider_name: "Google",
authorization_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://oauth2.googleapis.com/token",
client_id: "<your_client_id>.apps.googleusercontent.com",
scope: [
"https://www.googleapis.com/auth/spreadsheets.readonly",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
],
authorization_url_extras: {
prompt: "consent",
access_type: "offline",
},
identity_config: {
url: "https://www.googleapis.com/oauth2/v1/userinfo",
account_identifier: "$.email",
http_method_type: "GET",
},
use_pkce: false,
},
});
// ...

OAuth2 provider properties are described in the tables below.

OAuth2 provider properties

FieldTypeDescriptionRequired?
provider_keystringThe unique string identifier for a provider. An app cannot have two providers with the same unique identifier. Changing unique identifiers will be treated as the deletion of a provider. Providers with active tokens cannot be deleted.Required
provider_typeSchema.providers.oauth2The only supported provider type value at this time is Schema.providers.oauth2.CUSTOM.Required
optionsobjectObject with further provider-specific details. See the table below.Required
OAuth2 provider options properties
FieldTypeDescriptionRequired?
provider_namestringThe name of your provider.Required
client_idstringThe client ID from your provider.Required
authorization_urlstringAn OAuth2 requirement to complete the OAuth2 flow and to direct the user to the provider consent screen.Required
scopearrayAn OAuth2 requirement to complete the OAuth2 flow and to grant only the scopes provided to the access token.Required
identity_configobjectUsed to obtain user identity by finding the account associated with the access token. See the table below for details.Required
token_urlstringAn OAuth2 requirement to complete the OAuth2 flow and to exchange the code with an access token.Required
token_url_configobjectAn object that can further define a use_basic_auth_scheme object, which contains a solitary boolean property, use_basic_auth_scheme, that defines whether HTTP Basic Authentication should be used with the token_url field above. Defaults to false.Optional
authorization_url_extrasobjectHTTP request query parameters to attach to the authorization_url. Set object key names as query parameter names and object key values as query parameter values.Optional
use_pkcebooleanSpecifies if the provider uses PKCE. Defaults to false.Optional

OAuth2 provider identity_config properties

FieldTypeDescriptionRequired?
urlstringThe endpoint the provider exposes to fetch the user identity. It is used to identify the authenticated user.Required
account_identifierstringThe field name in the response from the above url field representing the user identity.Required
headersobjectExtra HTTP headers to attach to the request to the url field. Set object key names as header names and object key values as header values. Note: the Authorization header is automatically set by Slack.Optional
http_method_typestringThe HTTP method to employ when sending a request to the identity url. Defaults to GET. Acceptable values include GET or POST.Optional
bodyobjectHTTP body parameters that the identity url expects. Only used if http_method_type is set to POST. Set object key names as body parameter name and object key values as body parameter values.Optional
info

The identity_config field is used to extract an external_user_id; this value is then used to allow a single user to issue multiple tokens for multiple provider accounts. If a Slack user with multiple accounts extracts the same external_user_id from the provider for each of their accounts, the existing token will be overwritten, and they will not be able to use multiple accounts.

3. Add your OAuth2 provider to your manifest

In your manifest file, insert the newly-defined provider into the externalAuthProviders property (if that property doesn't exist yet, go ahead and create it):

export default Manifest({
//...
// Tell your app about your OAuth2 providers here:
externalAuthProviders: [GoogleProvider],
//...
});

Now, with your OAuth2 provider defined and your manifest configured to use it, you can encrypt and store your client secret so that your app's users can utilize the OAuth2 authorization flow.

4. Encrypt and store your client secret

Your app needs to be deployed to Slack once in order to create a place to store your encrypted client secret. Run the slack deploy command in your terminal window:

slack deploy

This command will bring up a list of currently authorized workspaces. Select the workspace where your app will exist, and wait for the CLI to finish deploying.

When finished, stay in your terminal window to add your client secret for the newly-defined provider, ensuring that you wrap the secret string in double quotes as follows:

slack external-auth add-secret --provider google --secret "GOCSPX-abc123..."

Running the add-secret command will bring up a list of workspaces available to you. Find and select the workspace you recently deployed your app to; you'll know it's the workspace you recently installed the app in by locating the item in the list with your app's name and ID (e.g., myapp A01BC...) rather than "App is not installed to this workspace."

info

If you get a provider_not_found error, go back to your manifest file and check to make sure that you included your OAuth2 provider in the externalAuthProviders properties of your manifest definition.

If everything was successful, the CLI will let you know:

✨  successfully added external auth client secret for google

Great! With your app configured to interact with your defined OAuth2 provider, we can now initialize the OAuth2 sign-in flow, connecting your external provider to your Slack app.

5. Initialize the OAuth2 flow

Once your provider's client secret has been added, it's time to create a token for your app to interact with your OAuth2 provider with external-auth add.

Run the following command:

slack external-auth add

This will display a list of workspaces your app is deployed to. Select the one you're currently working in. Upon selection, you'll be provided a list of all providers that have been defined for this app, along with whether there's a secret and token.

$ slack external-auth add

? Select a provider [Use arrows to move, type to filter]
> Provider Key: google
Provider Name: Google
Client ID: <your_id>.apps.googleusercontent.com
Client Secret Exists? Yes
Token Exists? No

If you have just created a provider, you'll notice that it reports no tokens existing. Let's go ahead and create a token by initializing the OAuth2 sign-in flow.

Select the provider you're working on, which will open a browser window for you to complete the OAuth2 sign-in flow according to your provider's requirements. You'll know you're successful when your browser sends you to a oauth2.slack.com page stating that your account was successfully connected.

Verify that a valid token has been created by re-running the external-auth add command:

slack external-auth add

? Select a provider [Use arrows to move, type to filter]
> Provider Key: google
Provider Name: Google
Client ID: <your_id>.apps.googleusercontent.com
Client Secret Exists? Yes
Token Exists? Yes

If you see Token Exists? Yes, that means a valid auth token has been created, and you're ready to use OAuth2 in your app! Exit out of this command flow by entering Ctrl+C in your terminal — otherwise you'll be guided through the OAuth2 sign-in flow again.

6. Add OAuth2 to your function

Your custom functions can leverage your provider's token by configuring it to receive a Schema.slack.types.oauth2 type as an input parameter to your function's definition.

Here's how that might look if we were to use the sample function from the starter template:

// functions/sample_function.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const SampleFunctionDefinition = DefineFunction({
callback_id: "sample_function",
title: "Sample function",
description: "A sample function",
source_file: "functions/sample_function.ts",
input_parameters: {
properties: {
message: {
type: Schema.types.string,
description: "Message to be posted",
},
// Define token here
googleAccessTokenId: {
type: Schema.slack.types.oauth2,
oauth2_provider_key: "google",
},
user: {
type: Schema.slack.types.user_id,
description: "The user invoking the workflow",
},
},
required: ["user"],
},
output_parameters: {
properties: {
updatedMsg: {
type: Schema.types.string,
description: "Updated message to be posted",
},
},
required: ["updatedMsg"],
},
});

export default SlackFunction(
SampleFunctionDefinition, // Define custom function
async ({ inputs, client }) => {
// Get the token:
const tokenResponse = await client.apps.auth.external.get({
external_token_id: inputs.googleAccessTokenId,
});
if (tokenResponse.error) {
const error =
`Failed to retrieve the external auth token due to ${tokenResponse.error}`;
return { error };
}

// If the token was retrieved successfully, use it:
const externalToken = tokenResponse.external_token;
// Make external API call with externalToken
const response = await fetch("https://somewhere.tld/myendpoint", {
headers: new Headers({
"Authorization": `Bearer ${externalToken}`,
"Content-Type": "application/x-www-form-urlencoded",
}),
});
if (response.status != 200) {
const body = await response.text();
const error =
`Failed to call my endpoint! (status: ${response.status}, body: ${body})`;
return { error };
}

// Do something here
const myApiResponse = await response.json();
const updatedMsg =
`:newspaper: Message for <@${inputs.user}>!\n\n>${myApiResponse}`;

return { outputs: { updatedMsg } };
},
);

7. Include OAuth2 input in a workflow step

Next, while configuring your workflow, choose the persona whose auth you want to use when the workflow runs. To do this, pass an object with a credential_source to the OAuth2 input in the step configuration.

info

Slack will automatically inject the correct token ID into the OAuth2 input property based on the selected credential_source; you do not need to provide a token ID here.

Using end user tokens

If you would like the workflow to use the account of the end user running the workflow, use credential_source: "END_USER".

The end user will be asked to authenticate with the external service in order to connect and grant Slack access to their account before running the workflow. This workflow can only be started by a Link Trigger, as this is the only type of trigger guaranteed to originate directly from an end-user interaction.

// Somewhere in your workflow's implementation:
const sampleFunctionStep = SampleWorkflow.addStep(SampleFunctionDefinition, {
user: SampleWorkflow.inputs.user,
googleAccessTokenId: {
credential_source: "END_USER"
},
});

Using developer tokens

If you would like the workflow to use the account of one of the app collaborators, use credential_source: "DEVELOPER".

// Somewhere in your workflow's implementation:
const sampleFunctionStep = SampleWorkflow.addStep(SampleFunctionDefinition, {
user: SampleWorkflow.inputs.user,
googleAccessTokenId: {
credential_source: "DEVELOPER"
},
});

After deploying the manifest changes above, you have to select a specific account for each of your workflows in this app. Assuming that you had run slack external-auth add before to add an external account, use the command slack external-auth select-auth as shown below:

slack external-auth select-auth
? Select a workspace <workspace_name> <workspace_id>
? Choose an app environment Deployed <app_id>
? Select a workflow Workflow: #/workflows/<workspace_name>
Providers:
Key: google, Name: Google, Selected Account: None

? Select a provider Key: google, Name: Google, Selected Account: None
? Select an external account Account: <your_id>@gmail.com, Last Updated: 2023-05-30

✨ Workflow #/workflows/<workspace_name> will use developer account <your_id>@gmail.com when making calls to google APIs

Multiple collaborators can exist for the same app and each collaborator can create a token using the slack external-auth add command. To select the appropriate collaborator account to run a specific workflow, the same slack external-auth select-auth command can be used. However, a collaborator needs to set up their own account using slack external-auth select-auth command by invoking this command. i.e. a collaborator cannot use slack external-auth select-auth to select auth for a workflow on behalf of another collaborator for the same app.

A collaborator can remove their account by running slack external-auth remove command. This would automatically delete the existing selected auths for each of the workflows that were using it. Therefore, in such a case, slack external-auth select-auth command would be needed to be invoked again before executing the relevant workflows successfully later.

8. Force refreshing a token programmatically

If you ever want to force a refresh of your external token as a part of error handling, retry mechanism, or something similar, you can use the sample code below:

// Somewhere in your functions error handling and retry logic:
const result = await client.apps.auth.external.get({
external_token_id: inputs.googleAccessTokenId,
force_refresh: true // default force_refresh is false
});

9. Deleting a token programmatically

If you ever want to delete your external token programmatically, you can use the sample code below. Bear in mind that once a token is deleted, all workflows that were previously using the token will no longer work.

This will not revoke the token from the provider's system. It will only delete the reference to the token from Slack and prevent it from being used within the external authentication system.

  // Somewhere in your function:
await client.apps.auth.external.delete({
external_token_id: inputs.googleAccessTokenId,
});

Delete external auth tokens

If you'd like to delete your tokens and remove OAuth2 authentication from your Slack app, the following commands will allow you to do so:

CommandDescription
$ slack external-auth removeChoose a provider to delete tokens for from the list displayed.
$ slack external-auth remove --allDelete all tokens for the app by specifying the --all flag.
$ slack external-auth remove --provider provider_name --<app_name>Delete all tokens for a provider by specifying the --provider flag.
info

This will not revoke the token from the provider's system. It will only delete the reference to the token from Slack and prevent it from being used within the external authentication system.

Troubleshooting

You can view external authentication logs via slack activity. These logs contain information about errors encountered by users during the OAuth2 exchange and workflow execution. Below are some common errors:

ErrorDescription
access_token_exchange_failedAn error was returned from the configured token_url.
external_user_identity_not_foundThe configured account_identifier was not found in user identity response.
internal_errorAn internal system error happened. Please reach out to Slack if this occurs consistently.
invalid_identity_config_responseurl in the configured identity_config returned an invalid response.
invalid_token_responsetoken_url returned an invalid response.
missing_client_secretOptional client secret was found for this provider.
no_refresh_tokenToken to refresh the expired access token does not exist.
oauth2_callback_errorThe OAuth2 provider returned an error.
oauth2_exchange_errorThere was an error while obtaining the OAuth2 token from the configured provider.
scope_mismatch_errorSlack was not able to find an OAuth2 token that matched the scope configured on your provider.
token_not_foundSlack was not able to find an OAuth2 token for this user and provider.

Next Steps

Check out the following sample projects to see how real-world workflow apps use OAuth:

  • Timesheet approval app uses Google Sheets to store information collected in a workflow form from Slack users
  • Simple survey app uses Google Sheets to store survey responses
  • GitHub functions repo brings oft-used GitHub functionality - such as creating new issues - to Slack using functions and workflows