ActionCable Devise Authentication

29 Aug 2015 By: Greg Molnar

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.

PS: If you want to get updates from me please subscribe to my email list.
I hate spam as much as you do, so I won't send you anything else than Ruby/Rails related updates occasionally, and of course you can unsubcribe anytime.