slack-ruby / slack-ruby-client

A Ruby and command-line client for the Slack Web, Real Time Messaging and Event APIs.
MIT License
1.21k stars 214 forks source link

Does slack-ruby-client include a method I can use for verifying Slack event requests? #238

Closed ndbroadbent closed 5 years ago

ndbroadbent commented 5 years ago

I've got the Events API working, and Slack is posting events to my server. I'm looking at the Verifying Requests From Slack documentation, and it doesn't mention Ruby under the "SDK support" section. Is that an oversight, or is there still no official way to verify Slack requests if you're using Ruby? I'm happy to follow the step-by-step instructions, but would prefer to use a library. Is there another Ruby gem that implements the same HMAC verification?

Thanks!

dblock commented 5 years ago

The library doesn't have much for slack events support, but this was discussed in #131. Currently there's no code here that verifies events are coming from slack. Any such code is welcome, please PR!

alexagranov commented 5 years ago

As @dblock pointed out, it's probably more appropriate to add a request validation helper in the repo that actually receives requests - slack-ruby-bot or slack-ruby-bot-server - but since you're asking here...

If your registered endpoint is something like /events and you're running a Sinatra server, you could do something like:

# Events API endpoint
post '/events' do
  body = ActiveSupport::JSON.decode(request.body.read)&.deep_symbolize_keys!
  halt 404 unless valid_token?(body[:token])
  ...
end

def valid_token?(token)
  token == Rails.configuration.x.slack.verification_token
end

You may notice that I'm referencing Rails.configuration which is possible by init'ing the Rails app via,

require ::File.expand_path('../config/environment', __FILE__)

from your rackup .ru file. You could also just use basic Sinatra configuration for your verification token - I happen to run slack-bot servers in tandem with a Rails app and wanted to share the same config.

Note that the token will be in params for slash command and action callback endpoints, the events API endpoint is yet another endpoint.

jmanian commented 5 years ago

It could actually be useful to have a simple method as part of slack-ruby-client that implements Slack's signature verification procedure. (Slack rolled out request signing a while back; the old token verification system is deprecated.)

Even just a method to take care of the hashing perhaps, so that you could do something like this:

Slack::Events.valid_signature?(signature, timestamp, body, secret)

For reference, my implementation as a controller filter in Rails looks like this

  def verify_signature
    signing_secret = ENV['SLACK_SIGNING_SECRET']
    version_number = 'v0' # always v0 for now
    timestamp = request.headers['X-Slack-Request-Timestamp']
    raw_body = request.body.read # raw body JSON string

    if Time.at(timestamp.to_i) < 5.minutes.ago
      # could be a replay attack
      render nothing: true, status: :bad_request
      return
    end

    sig_basestring = [version_number, timestamp, raw_body].join(':')
    digest = OpenSSL::Digest::SHA256.new
    hex_hash = OpenSSL::HMAC.hexdigest(digest, signing_secret, sig_basestring)
    computed_signature = [version_number, hex_hash].join('=')
    slack_signature = request.headers['X-Slack-Signature']

    if computed_signature != slack_signature
      render nothing: true, status: :unauthorized
    end
  end

Edit: If this gets added, I imagine it should have an option for changing the 5-minute cutoff to any duration, or no cutoff at all. There should also be an option to provide a different version string.

dblock commented 5 years ago

Again, I would take a PR along these lines.

dblock commented 5 years ago

Needed this in slack-strava, so took a stab at it in https://github.com/slack-ruby/slack-ruby-client/pull/245.