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
# 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
....
# 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
...
Did you enjoy reading this? Sign up to the Rails Tricks newsletter for more content like this!
Or follow me on Twitter
I run an indie startup providing vulnerability scanning for your Ruby on Rails app.
It is free to use at the moment, and I am grateful for any feedback about it.If you would like to give it a spin, you can do it here: Vulnerability Scanning for your Ruby on Rails app!