Introduction
This is the second part of this series posts about creating Slack Command APIs with Ruby. In the first post, we’ve setup a new application that returns OK to the command caller.
In this post, we’re going to talk about how to authorize Slack Commands requests in order to avoid invalid requests in our API.
Validating the request via token
According to Slack Commands API documentation:
When someone types a slash command, the message (and its data) will be sent to the configured external URL via
HTTP POST
. It's up to you, the developer, to do something with the message data and respond back, if desired.
The POST body message has the following params structure:
{
"token"=>"XXXXX",
"team_id"=>"YYYY",
"team_domain"=>"ZZZZ",
"channel_id"=>"UUUU",
"channel_name"=>"directmessage",
"user_id"=>"U1234567",
"user_name"=>"anderson",
"command"=>"/congratulate",
"text"=>"@john for his new product release! It's brilliant!",
"response_url"=>"https://hooks.slack.com/commands/YYYY/DDDDD/HASH"
}
And, It’s our responsability to check if the message is valid or not “by confirming that the token
value matches the validation token you received from Slack when creating the command.”
Server-side validation
We’re going to create a Rack Middleware that intercepts our request and validates the token
param.
If the token matches, we should continue to return “OK” to the user. If the token is invalid we must return a specific message telling the Slack user to setup the correct token in the application.
The Rack Middleware
Rack Middlewares are useful to intercept requests without changing the actual handler method. In this case, we’re going to create a Middleware called SlackAuthorizer.
It needs to implement two methods:
initialize(app)
: responsible for receiving the application contextcall(env)
: responsible for handling the actual request, it must return a valid rack message, composed by an array with the status code, header hash and an array with the body message.
Here is the actual implementation of the SlackAuthorizer class:
# app/slack_authorizer.rbclass SlackAuthorizer
UNAUTHORIZED_MESSAGE = 'Ops! Looks like the application is not authorized! Please review the token configuration.'.freeze
UNAUTHORIZED_RESPONSE = ['200', {'Content-Type' => 'text'}, [UNAUTHORIZED_MESSAGE]] def initialize(app)
@app = app
end def call(env)
req = Rack::Request.new(env)
if req.params['token'] == ENV['SLACK_TOKEN']
@app.call(env)
else
UNAUTHORIZED_RESPONSE
end
end
end
As you may see if the token
param matches the SLACK_TOKEN
env variable we send the request back to the application, else we should respond with the unauthorized rack response.
Notice that we respond with a 200 status. That’s because Slack expects 200 responses, even when it has any unexpected behavior we should respond with 200 status for consistency.
Plug in the Middleware
The next step is to use the Middleware in the application. Sinatra makes it easy. We need to literally use
it inside the body of our Sinatra application:
require 'sinatra'
require_relative 'app/slack_authorizer'use SlackAuthorizerpost '/slack/command' do
'OK'
end
Setup the token in production
The final step is to deploy the application, setup the token env var and test it.
In order to deploy a new version of the application we must send it to Heroku via git:
$ git add .
$ git commit -m 'Adding token authentication'
$ git push heroku master
OK. The new application version is now deployed, but we’ve not configured the token already. Let’s test if the middleware is up.
Run the following command in Slack:
/congratulate @john for doing a great job!
You must see something like that:
Great! Let’s setup the token env var in the Heroku application:
Test it
Now everything is setup, let’s send the /congratulate command again and see what happens:
/congratulate @john for doing a great job!
It means we’ve configured the correct verification token in Heroku.
Conclusion
Leaving the API open to any request is insecure and it’s up to us to protect the application against these invalid POST messages.
In a real world application, you’ll not use the Middleware the way we did it because it’s going to require the token in any route call. We can create multiple Sinatra::Base applications in the same Sinatra app and use the Middleware in the one responsible for slack commands, for instance.
In the next post will see how to validate the income params and how to return the correct feedback to users.
Follow the step by step coding at: