Click here to Skip to main content
13,904,127 members
Click here to Skip to main content
Add your own
alternative version

Stats

2.9K views
16 downloads
5 bookmarked
Posted 21 Feb 2019
Licenced Apache

Integrate Any Application with Slack

, 21 Feb 2019
Rate this:
Please Sign up or sign in to vote.
This article demonstrates how to integrate any application with slack and utilise your application capabilities in slack interface.

Introduction

This article shows how any application can be connected to any slack workspace where application users can authenticate from slack and perform activities from slack which communicates to the application. Also, application can communicate back to the slack workspace.

Background

Throughout my life, I came accross various ways to communicate with application. Take an example, say dropbox app, you can see that there are lot of ways to communicate with it, e.g., web app, various types of mobile apps, voice apps such as alex app for dropbox and what not! Now, how would you react if your most favourite tool (i.e., Slack) lets you connect to dropbox, it is pretty much time saving, isn't it! You don't have to deviate from your work, just integrate dropbox and issue some command to get your output. 

Now, apply the above thought process about your consumers of your application. Think of this article as a helper guide to enable one more medium/interface for your pro-users/pro-consumer of your application. This will enhance their way of talking to your application and get more productive with your application, i.e., one more selling point for your product/application.

In case you are wondering about Slack, here is little more background about it. It is a team collaboration tool, which lets you create a workspace which belongs to your organization. Inside your organization, you can build closed group or open groups which are named as channels. Then, every person can communicate with other person in your organization.

Scope

If you read the background, there is huge scope of enhancing your application by integrating it. However, in this article, the scope is limited. From now on, instead of speaking application, I am going to take a real life example. Here, I have taken "Buzz Forum"  as application. This application lets you sign up for it. Admins can create groups consisting of some members. Members can ask a question and other members can post answers, that's all about the app. This application uses "oauth" for authentication. It provides APIs to fetch groups, supports webhook for event, etc.. This application is hosted in public website. For demo purposes, I am not giving any further details about the "Buzz forum"'s application URL, rather I will use it as "http://app.example.com". Since this app is mainly for "coder", I am going to use "coder" app from now on.

What is included in the demo:

  • Build Slack app with required settings
  • Install the Slack app to specific channel
  • Sign in to coder app from Slack
  • Fetch all groups form coder app from Slack
  • Subscribe to any group from Slack
  • If any new question is posted in coder, post update to Slack channel

There is some limitation for the demo.

  • To make Slack app distributable, we have to use HTTPS (in all required URLs) for Slack hosted app. In this article, I am using HTTP, which makes it non distributable and I will refer to the old demo when it was allowed to install HTTP enabled app via URL.
  • This integration is applicable to oauth enabled application, preferably web application. However, in future, if there is a request for other type of auth, I can take a look.
  • I am using node.js for this demo. All required credential / config is saved in file sytem instead of DB which is more recommended.
  • I will concentrate more on Slack-coder app integration code instead of coder app's functionality. All URLs are purposefully hidden and instead used domain name as "example.com".
  • The integration mainly concentrates on showing capability rather than focusing on coding standard obviously which can be improved.

Prerequisites

  • I have coder application's oauth credential, i.e., client_id, client_secret, scope, redirect_url. It is hosted - "https://app.example.com".
  • I have created a Slack account using this - https://slack.com/get-started. Then I created Slack channel named "code-project-v1".
  • I have a valid credential for coder application.
  • Slack-coder app is hosted here - http://slack.example.com. It has to be publicly hosted/accessible.
  • Node and npm need to be installed. Node version higher than 10 and npm version higher than 5.

Using the Code

I have used node.js, here are the dependencies needed.

"dependencies": {
  "body-parser": "^1.18.3",
  "express": "^4.16.4",
  "querystring": "^0.2.0",
  "request": "^2.88.0"
}

The project mainly contains 2 files, one is for settings (settings.js), one is for handling the slack callbacks (server.js).

Create Slack App

Navigate to https://api.slack.com/apps?new_app=1 and create a new app. In the app name, I used "CoderApp" and in workspace, I chose my existing workspace. Here is the dialog shown:

The next step is to configure some required settings.

OAuth & Permission

Usage: This is used to specify the callbacks from slack when "CoderApp" (i.e., the Slack app) is installed in some other workspace. Since we will post our application info to a channel, we will need permission to post it.

How to set: Follow the screenshot (on the Left side, click on "OAuth & Permission"). In the redirect URL, I used http://slack.example.com/slack. Permission I used "Post to specific channels in Slack".

 

Slash Command

Usage: This enables to add custom Stack command. You might know the existing slash command, e.g.,  "/remind" , "/archive" etc.. Here, we will introduce "/coder" as slash command. Using that command, you can signin and list groups.

How to set: Follow the screenshot (on the Left side, click on "Slash command"). Here, in the request URL, I used http://slack.example.com/slack_cmd.

Interactive Command

Usage: This enables slack user to execute some user input which in turn contacts to our integration app. Here, we utilise that to subscribe to a group. 

How to setup: Follow the screenshot (On the left side, click on "Interactive command"). Here, the request URL is set as "http://slack.example.com/slack_interactive".

 

Here, we are done with setting of Slack app. Now, go to basic information on the left side, note down the Slack app id, client id, client secret, etc. We will use that in the next step. Here is the basic information shown for me.

 

Making Our Application Be Installable in Other Workspace

In OAuth redirect URL is set as "http://slack.example.com/slack". Here is how it is handled in code:

app.get('/slack', function (req, res) {
   settings.slack_callback_data.formData["code"] = req.query.code;
   slack_response_on_install = res;
   console.log("code = " + req.query.code);
   console.log( settings.slack_callback_data);
   request(settings.slack_callback_data, function (error, response, body) {
      if (error) throw new Error(error);
      console.log(body);
      json_body = JSON.parse(body);
      file_name = "data/" + json_body.team_name.replace(/\s+/g, '').toLowerCase() + ".json";
      fs.writeFile(file_name, body, function (err) {});
      sendSlackMsg(json_body.incoming_webhook.url);
      slack_response_on_install.send("Welcome : " + json_body.team_name + 
          " to coder slack connect app."); // This will display the message on installation page.
      slack_response_on_install = null;
   });
})

In the settings, I have maintained slack_callback_data which contains all Slack credentials, refer to the next code snippet. When someone installs our Slack app, Slack invokes our "/slack" endpoint - this is as per redirect URL set, it also sends a code in querystring. We read that code and again post to https://slack.com/api/oauth.access to get access token of Slack workspace (of that Slack user who installs our app). After we get that, we will save that to file system (data/{slack_workspace_name}.json). Then, we will send notification saying "Welcome : {slack_workspace_name}" to coder Slack connect app.

Here is the snippet for slack_callback_data.

slack_callback_data: {
  method: 'POST',
  url: 'https://slack.com/api/oauth.access',
  headers: {
    'cache-control': 'no-cache',
    'content-type': 'multipart/form-data;'
  },
  formData: {
    client_id: "XXX.XXX",
    client_secret: "XXXXXXX"
  }
}

After this, we will get Slack credential for that client out of which incoming_webhook is important to us and we will use that to send message to Slack channel.

Authenticate to Coder App From Slack

This is one of the trickiest parts of this article. Here, we will map Slack user to coder app user. You might wonder why we would need this. Let me tell you, from Slack, we enabled to interact with our app, but we have to tell our integration app that any request coming from Slack is authenticated to interact with code app. Here, Slack user has to first authenticate with coder app to get updates about group which they select. They have to go the specific channel where app is installed, there they have to type "/coder signin" which will present them the sign in button. On clicking that, it will present them coder app login screen followed by oauth consent screen, then if all goes well, it will say, "Thanks for connecting the coder App" .

How it will work: In the slash command, we added support from /coder signin and url to act on is "http://slack.example.com/slack_cmd". Here is the server side code:

app.post('/slack_cmd', function (req, res) {
   console.log("----- slack command -----");
   console.log(req.body);
   command_reply_url = req.body.response_url;
   signin_token_file = "data/" + req.body.team_domain + "-app-access.json";
   var commands = req.body.text.split(" ");
   if (commands[0] == "signin") {
      res.send(settings.signin_option);
   } else if (commands[0] == "groups") {
      no_of_comm = commands[1];;
      loadGroups("data/" + req.body.team_domain + "-app-access.json", no_of_comm);
      res.send("You will receive groups shortly");
   } else {
      res.send(req.body.text + " is not supported, only `[signin, groups]` supported");
      command_reply_url = null;
      signin_token_file = null;
   }
})

When signin command is send, then it will send settings.signin_option to respond. Here is the snippet for signin_option.

signin_option: {
  "text": "Authenticate with coder portal",
  "attachments": [{
    "text": "Click signin",
    "fallback": "You are unable to authenticate",
    "callback_id": "signin_callback",
    "color": "#3AA3E3",
    "attachment_type": "default",
    "actions": [{
      "name": "signin",
      "text": "Signin",
      "type": "button",
      "url": "http://slack.example.com/coder_signin",
      "value": "signin"
    }]
  }]
}

Here is how it will look:

Here the action url is http://slack.example.com/coder_signin, i.e., on server side, we have handler for coder_signin, here is that.

// Purpose: on signin request redirect to our App oauth URL
//          once it gets code from redirection, it will request for access_token
//          also it will save the access tocken to /data/teamname-app-access.json
app.get('/coder_signin', function (req, res) {
   console.log("----- coder signin start -----");
   app_response = res;
   if (req.query.code) {
      settings.app_auth.form['code'] = req.query.code;
      request(settings.app_auth, function (error, response, body) {
         if (error) throw new Error(error);
         fs.writeFile(signin_token_file, body, function (err) {});
         onAppSuccessSendToSlack(settings.app_connect_msg);
         signin_token_file = null;
      });
   } else {
      res.writeHead(302, {
         Location: settings.app_init_auth
      });
      res.end();
   }
})

Here, it will do oauth for coder app, i.e., first, it will redirect to settings.app_init_auth (in "else" part), i.e., "http://app.example.com/oauth/authorize?response_type=code&client_id={coder_client_id}&redirect_uri=http://slack.example.com/coder_signin".

Then, coder app will send the code in redirect url, again same handler is called where it received the code in "if " block. Again, it will post to "http://app.example.com/oauth/token" with code, client secret to get the access_token, refresh token, etc. Here is the snippet for settings.app_auth:

app_auth: {
  method: 'POST',
  url: 'http://app.example.com/oauth/token',
  headers: {
    'cache-control': 'no-cache',
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  form: {
    grant_type: 'authorization_code',
    client_id: "coder_client_id",
    client_secret: "coder_client_secret"
  }
},

After all this is done, we save all token to "data/teamname-app-access.json" and send notification to Slack channel, saying Thanks for connecting the coder App.

Here is the snippet for onAppSuccessSendToSlack.

// Purpose: On successfully coder App Authentication, Send message to slack
function onAppSuccessSendToSlack(message) {
   sendSlackMsg(command_reply_url, message, function(){
      if (app_response) {
         app_response.send("You may close this tab and open slack");
      }
      app_response = null;
      command_reply_url = null;
   })
}


// Purpose: Send a slack message and execute callback if supplied
function sendSlackMsg(url, message, callback) {
   settings.post_message["url"] = url;
   if (message) {
      settings.post_message.json.text = message;
   }
   request(settings.post_message, function (error, response, body) {
      if (error) throw new Error(error);
      console.log(body);
      if (callback) callback();
   });
}

Here is how it will look when signin is clicked. Currently, I signed in to coder app, so no login or consent is shown.

Listen to Coder Groups Command

Refer to "/slack_cmd" handler, there we defined that if we get groups, then call loadGroups method. User will send "/coder groups {count_of_groups}"  e.g. "/coder groups 2". This will fetch 2 latest groups from coder app and present it in Slack showing option to subscribe to one of the groups. 

Here is the snippet for loadGroups:

function loadGroups(team_name, no_of_grp) {
   refreshTheToken(team_name, function(access_token){
      settings.app_groups_api.headers["Authorization"] = 'Bearer ' + access_token;
      settings.app_groups_api.qs.limit = no_of_grp;
      request(settings.app_groups_api, function (error, response, body) {
         if (error) throw new Error(error);
         console.log("Found groups");
         console.log(body);
         body = JSON.parse(body);
         content = {};
         content["text"] = "Total groups: " + body.total_count;
         content["attachments"] = [];
         for (i = 0; i < body.content.length; i++) {
            content["attachments"].push(buildGroupContent(body.content[i]));
         }
         sendSlackMsgWithObject(command_reply_url,content, function () {
             console.log("Group fetch msg sent")
         });
      });
   })    
}

Here, I am calling refreshTheToken which gets fresh coder app token so as to avoid expired token, then it invokes coder app's group_api URL with required access token (of coder app) which gets us list of groups, then we build nicely formatted Slack compatible JSON and send it to Slack via the command_reply_url (which is the req.body.response_url of /slack_cmd). Please refer to the actual codebase for function definitions of buildGroupContent and sendSlackMsgWithObject.

Screenshot for group command:

Listen to Subscribe Button Click in Slack

We have defined Slack interactive command which comes to picture now, there the request URL is "http://slack.example.com/slack_interactive". Here is the snippet for that.

// Purpose: On slack action : such as Post_Idea/ subscribe to challenge
app.post('/slack_interactive', function (req, res) {
   console.log("----- slack interactive -----");
   console.log(req.body);
   if(req.body.payload){
      payload = JSON.parse(req.body.payload);
      if(payload.actions && payload.actions[0].value == "subscribe"){
         comm_id = payload.callback_id.split("_")[1];
         subscribeToGroup(comm_id, payload.team.domain);
         res.send({
            text: "Thanks " + payload.team.domain + " for subscribing to groups!"
         });
      } else {
         res.send({
            text: "listening"
         });
      }
   }
})

Here, we get the payload from req.body.payload and it checks if command is subscribe, then calls subscribeToGroup which saves the group-id in file system location is "data/{slack_workspace}-subscribe.json", it also tell coderapp which Slack channel subscribed to what group. This will help us in sending notification to the concerned Slack channel whenever any group activity happens. Then, we respond to Slack command by sending text "Thanks {channelname} for subscribe to groups!" to response object. 

Send Notification to Slack from Code Application

This is the last article where we will send notification to Slack if any new question is posted to coder app. To support this, I have added one endpoint, here is the snippet.

// Purpose: read querystring which should be team_name and message, 
// then send slack message to that team's group
app.get('/message', function (req, res) {
   client = req.query.client;
   var object = JSON.parse(fs.readFileSync('data/' + client + ".json", 'utf8'));
   sendSlackMsg(object.incoming_webhook.url, req.query.msg);
   res.send("msg will be delivered");
})

Whenever there is any event in coder application, we will just post a message via "http://slack.example.com/message?client={slack_workspace}&msg={actual_msg}" . Here is the screenshot of how it looks:

 

Points of Interest

This article helped me integrate coder app to Slack for mainly information posting, there are future plans to support adding question or answer via Slack. If you have done any such integration, I would love to know about this, please post about it or give any suggestions here.

History

This is the current version of my article, however if I come accross any suggestion or improvement, I will post it here.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

Share

About the Author

ArindamNayak
Software Developer
India India
Developing web based application since last 5 years, also interested in designing application with optimized used of available tech stacks, as well as apply those and real life experience to help out friends, colleagues,communities such as this or other forums.

MCSD (2013) certified developer.

Reach me - http://about.me/arindamnayak

You may also be interested in...

Pro

Comments and Discussions

 
QuestionNice Article Pin
Member 1324062621-Feb-19 22:05
memberMember 1324062621-Feb-19 22:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 22 Feb 2019
Article Copyright 2019 by ArindamNayak
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid