This is the third blog post of a five-part series.
Building a Slack app that integrates with Salesforce involves some challenges, such as knowing the right integration capability to use on each case, picking the right authorization flow, and implementing it securely. This is the third blog post of a series in which we cover the whole process of creating a Slack app that integrates with Salesforce from scratch. Learn along with us while we build Ready to Fly, our new sample app for submitting and approving travel requests in Salesforce without leaving Slack.
Note: some parts of this app have been live coded by my colleagues Mohith Shrivastava (@msrivastav13) and Kevin Poorman (@codefriar) in their codeLive video series: Building with Slack.
Check out the rest of the blogs in this series here:
- Part 1 – Architectural Overview
- Part 2 – Bolt
- Part 4 – Local Development and Debugging
- Part 5 – What’s Coming Next
As we promised at the beginning of this series, in this post we’ll focus on explaining how the Node.js app interacts with Salesforce using the library JSforce.
Obtaining authorization with the user-to-user flow
One requirement of Ready to Fly (and probably 90% of these kind of apps) was the ability to perform operations in Salesforce on behalf of each individual user. For example, if the Slack user “Marc Benioff” submits a travel request, the owner of the travel request record that gets created in Salesforce should be the Salesforce user for “Marc Benioff.” In order to do that, we implemented the OAuth 2.0 Web Server Flow for Web App Integration (commonly known as the “user-to-user” flow). Let’s dig into how this flow works.
When you open Ready to Fly, the Node.js app checks to see if you’ve authorized with Salesforce in the past. If that is not the case, you are prompted to authorize with Salesforce.
When you click on the “Authorize with Salesforce” button, you’re redirected to the Salesforce org in which the Salesforce portion of the app is deployed, so you can log in with your credentials. If the login is successful, you are redirected to a callback URL. That callback URL is exposed in the Node.js app as a custom HTTP route, a concept that we explained in the second blog post of this series.
Salesforce returns an access and a refresh token to the custom HTTP route that the Node.js app can use in subsequent requests to perform actions in Salesforce on your behalf.
Note that to keep track of the user who’s authorizing during the Salesforce OAuth flow dance, we took advantage of
express-session (see docs), which is an npm module to manage sessions in the server.
Storying tokens securely
To avoid having to go through this whole authorization process next time that you use the app, the tokens need to be persisted. For maximum security, we decided to store our tokens inside our Salesforce org as records of a custom object known as
Slack_Authentication__c that’s deployed to Salesforce as part of the app. We also store the Slack User ID — Salesforce User ID mapping:
To provide additional security, tokens are encrypted in transit using the AES256 algorithm.
Querying tokens with the server-to-server flow
When you open the Slack app, it doesn’t know whether or not you’ve authorized with Salesforce in the past. The Node.js app needs to query Salesforce for the tokens, and this operation needs to be done using an integration user. To that end, we implemented the OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration (commonly known as the server-to-server flow).
Using the server-to-server flow, the Node.js app queries the Salesforce org for the Slack user’s auth tokens. If they’re found, those tokens are used to make subsequent calls using the user auth flow. If they are not found, the user is redirected to the authorize page. Note that to speed up the process, we implemented a token cache on the Node.js app.
In summary, these are the three possible scenarios for the authorization process:
Scenario 1: The user authorized previously and the tokens are cached in the Node.js app. We don’t need to query Salesforce.
Scenario 2: The user authorized previously, but the tokens are not cached in the Node.js app (maybe just because the cache expired). In that case, we do need to query the
Slack_Authentication__c object in Salesforce using the JWT flow (server-to-server). In this scenario, we assume that the tokens for the user are stored in the object.
Scenario 3: The user did not authorize previously. The app detects that the tokens are not cached in the Node.js app. The app then queries the
Slack_Authentication__c object in Salesforce using the JWT flow. As the tokens are not there, the user is prompted to authenticate. The Web Server flow (user-to-user) handshake takes place. If the user is correctly authorized, the tokens are returned to the Node.js app. Finally, the Node.js app upserts them in Salesforce and stores them in the token cache.
Once the user is authorized, all requests are performed using the Web Server flow — remember, this is the one in which we perform requests on behalf of each individual user. To implement that, we took advantage of a Slack feature that allows you to inject code to be executed before every listener: a global middleware.
Salesforce Slack Starter Kit
While planning how we were going to build Ready to Fly, we thought that all the Salesforce authorization setup code could be reused, making easier for developers to scaffold a Slack app that integrates with Salesforce from zero. That’s why we decided to store that portion of the code in an individual repo called the Salesforce Slack Starter Kit and provide a deploy script to set an app up from scratch. Indeed, we first worked in the starter kit, and then we used it to create Ready to Fly. Make sure to take a look at it if you want to start a Salesforce + Slack app!
Calling from Salesforce to Slack
One of our requirements for Ready to Fly was to be able to post messages from Salesforce to Slack. There are several Slack features that allow you to do that.
One option is to use incoming webhooks. Incoming webhooks are defined in the Slack configuration page (or the app manifest), and they give you a unique URL to which you send a JSON payload with the message. The payload can be built with Block Kit.
Our case was a bit more complex because we wanted to execute some logic when the message arrives. In that case, you need to define a custom HTTP route.
Our messages’ custom HTTP route checks that the origin of the request was Salesforce. We do this by adding an HMAC signature to the HTTP requests in Salesforce. The signature is computed using a secret key that both Salesforce and Slack know and the body of the message.
In the Node.js app, we recompute the signature and make sure that it’s equal to the received one. This ensures the integrity and authenticity of the message.
That’s all for this blog post. I hope that you now have a good understanding of the key factors to have in mind when integrating Slack apps with Salesforce using Bolt. If you want to scaffold an app like this one from scratch, remember that the Salesforce Slack Starter Kit can vastly simplify the process. Take a look at this Youtube series in which my colleagues Mo & Kevin live coded it!
Look out for the next post in this series in which we’ll discuss how to deploy and debug the app locally.
About the author
Alba Rivas works as a Principal Developer Advocate at Salesforce. She focuses on Lightning Web Components and Lightning adoption strategy. You can follow her on Twitter @AlbaSFDC.