Did you know that you can navigate the posts by swiping left and right?

ActionCable Devise Authentication

29 Aug 2015 . category: rails . Comments
#ruby #rails #actioncable

ActionCable is a new framework for real-time communication over websockets and it will be part of Rails 5. I am not going to get into too much detail about it, you can read the very detailed readme of the project on this link: ActionCable.

The websockets server is running in a separate process from the main Rails application which means you need to authenticate your users there too. In the example app, David used a simple cookie based authentication in the app itself and re-validated the cookie at the websocket connection. This is good for demonstration, but many of the Rails based apps are using Devise for authentication so I want to share, how I solved the authentication with Devise.

The websocket server doesn't have a session, but it can read the same cookies as the main app, so I figured, I will just set a cookie with the user id and verify that at the socket connection. To do this, I used a Warden hook:

# app/config/initializers/warden_hooks.rb
Warden::Manager.after_set_user do |user,auth,opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = user.id
end
# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      logger.add_tags 'ActionCable', current_user.name
    end

    protected
      def find_verified_user
        if verified_user = User.find_by(id: cookies.signed['user.id'])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

This is nice and simple, but I needed some sort of a timeout to expire the session, so I set an expiry time too in the cookies:

# app/config/initializers/warden_hooks.rb
Warden::Manager.after_set_user do |user,auth,opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = user.id
  auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end
# app/channels/application_cable/connection.rb
...
protected
  def find_verified_user
    verified_user = User.find_by(id: cookies.signed['user.id'])
    if verified_user && cookies.signed['user.expires_at'] > Time.now
      verified_user
    else
      reject_unauthorized_connection
    end
  end
....

One thing left, is to invalidate the cookie on sign out, which can be done in another Warden hook:

# app/config/initializers/warden_hooks.rb
...

Warden::Manager.before_logout do |user, auth, opts|
  scope = opts[:scope]
  auth.cookies.signed["#{scope}.id"] = nil
  auth.cookies.signed["#{scope}.expires_at"] = nil
end
...

That’s it, now I can share the Devise authentication with my websocket server. If you want to see this in an example, you can check my fork of the actioncable-example.

Do you want to shape up your Rails security skills?

Although Rails is quite secure by default, you can still easily shoot yourself in the leg, make silly mistakes and get hacked. I am working on a course, in which I will show you how an attacker would try to hack a Rails application and the best techniques to prevent it.