ActionCable Devise Authentication

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.

Comments

Popular Posts

How to pass hash in Postman

nginx: unrecognized service

Bootstrap Select Picker append add new item if search not exist

Reading Excel Sheets using "Roo" gem in ruby on rails

Add CORS to Nginx on AWS Elastic Beanstalk

Enable gzip compression on Elastic Beanstalk with nginx

Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'

Get video duration by URL in Ruby on Rails

site-enables nginx setting in ruby in rails