Why am I not able to retrieve current_user
inside my channel or how should I retrieve current_user
?
What do I use?
Well, in theory:
ActiveCable::Connection
class.cookies.signed
and cookies.encrypted
So, if you know the name of your session cookie (somehow obvious, let it be called "_session
"), you can simply receive the data in it by:
cookies.encrypted['_session']
So, you should be able to do something like:
user_id = cookies.encrypted['_session']['user_id']
This depends on do you use cookie store for the session and on the exact authentication approach, but in any case the data you need should be there.
I found this approach more convenient as the session is already managed by the authentication solution you use and you more likely don't need to care about things like cookie expiration and duplication of the authentication logic.
Here is more complete example:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
session = cookies.encrypted['_session']
user_id = session['user_id'] if session.present?
self.current_user = (user_id.present? && User.find_by(id: user_id))
reject_unauthorized_connection unless current_user
end
end
end
if you are using devise gems in rails, Please replace this function:
def find_verified_user # this checks whether a user is authenticated with devise
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
I hope this will help you.
If you see the doc you provided, you will know that identified_by
is not a method for a Channel
instance. It is a method for Actioncable::Connection
.
From Rails guide for Actioncable Overview, this is how a Connection
class looks like:
#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
end
private
def find_verified_user
if current_user = User.find_by(id: cookies.signed[:user_id])
current_user
else
reject_unauthorized_connection
end
end
end
end
As you can see, current_user
is not available here. Instead, you have to create a current_user
here in connection.
The websocket server doesn't have a session, but it can read the same cookies as the main app. So I guess, you need to save cookie after authentication.
After setting self.current_user
in ApplicationCable::Connection
it become available in the channel instances.
So you can set up your authentication like Sajan wrote and just use current_user
in MessageChannel
For example this code worked for me
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :verified_user
def connect
self.verified_user = find_verified_user
end
private
def current_user
jwt = cookies.signed[:jwt]
return unless jwt
decoded = JwtDecodingService.new(jwt).decrypt!
@current_user ||= User.find(decoded['sub']['user_id'])
end
def find_verified_user
current_user || reject_unauthorized_connection
end
end
end
class NextFeaturedPlaylistChannel < ApplicationCable::Channel
def subscribed
stream_from "next_featured_playlist_#{verified_user.id}"
end
end
For Rails 5 API mode:
application_controller.rb
class ApplicationController < ActionController::API
include ActionController::Cookies
...
token = request.headers["Authorization"].to_s
user = User.find_by(authentication_token: token)
cookies.signed[:user_id] = user.try(:id)
connection.rb
class Connection < ActionCable::Connection::Base
include ActionController::Cookies
...
if cookies.signed[:user_id] && current_user = User.where(id: cookies.signed[:user_id]).last
current_user
else
reject_unauthorized_connection
end
config/application.rb
config.middleware.use ActionDispatch::Cookies