connection.rb in actioncable chatroom series, if not using Devise for authentication?
I'm watching https://gorails.com/series/realtime-group-chat-with-actioncable
and i noticed that in channels/application_cable/connection.rb , chris put this code to find verified users.
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags "ActionCable", "User #{current_user.id}"
end
protected
def find_verified_user
if current_user = env['warden'].user
current_user
else
reject_unauthorized_connection
end
end
end
end
The part i'm particuarly having problem with is the 'env['warden'].user part. I'm not using devise because i created my authentication from scratch...so what would it be than?
If you rolled your own authentication then really you're just about the only one that could answer that since it depends on how you implimented it and store the user session.
The best I can surmise, the point of the snippet is that the typical current_user object provided by devise isn't available due to channels not being able to access the current session (see notes) - so in order to check the current_user, we have to go directly to the warden middleware and snag the user object.
The WebSocket server doesn't have access to the session, but it has access to the cookies. This can be used when you need to handle authentication.
You may be able to do something with this method using cookies: http://www.rubytutorial.io/actioncable-devise-authentication/
Check out:
https://github.com/hassox/warden/wiki/Overview
https://github.com/hassox/warden/blob/master/lib/warden/proxy.rb#L184
https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb#L37
Like Jacob said, that would be up to your implemenation.
find_verified_user
just needs you to return the User object. That's what you get from warden, so your implemenation will be similar, just however you retrieve the User object in your setup and that should do the trick.
Hey guys, i'm still a bit stuck on this. So i'm hoping one of you guys can tell me what i'm doing wrong.
I thought it would be either these two codes, but it doesn't seem so...
What I've tried:
verified_user = User.find_by(id: params[:id])
and verified_user = User.find_by(id: session[:user_id])
Here are my two files for authentication:
class SessionsController < ApplicationController
def create
user = User.find_by(email: session_params[:email].downcase)
respond_to do |format|
if user && user.authenticate(session_params[:password])
session[:user_id] = user.id
format.html { redirect_to conversations_path }
format.json { render json: { success: true, user: user }, status: :ok }
else
format.html {
flash[:notice] = 'Incorrect user or password'
render 'new'
}
format.json {
render json: { success: false, error: 'Incorrect user or password' }, status: :unprocessable_entity
}
end
end
end
def destroy
session.delete(:user_id)
reset_session
redirect_to signin_path
end
private
def session_params
params.require(:session).permit!
end
end
class RegistrationsController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(user_params.merge({
is_admin: true
}))
respond_to do |format|
if @user.save
session[:user_id] = @user.id
format.html { redirect_to sites_path, notice: 'Successfully registered!' }
format.json { render :show, status: :created, location: @user }
else
format.html { render :new }
format.json { render json: user.errors, status: :unprocessable_entity }
end
end
end
def user_params
params.require(:user).permit!
end
end
So you set your user id in the session
. Just use that to look up the user for the websocket:
def find_verified_user
if current_user = User.find(session[:user_id])
current_user
else
reject_unauthorized_connection
end
end
So when I use Chrises code, it's still doing the same thing.
Except when I went back to my rails logs , i see an error like this
There was an exception - NameError(undefined local variable or method `session' for #<ApplicationCable::Connection:0x007fd5689d0860>) /home/ubuntu/workspace/app/channels/application_cable/connection.rb:14:in `find_verified_user' /home/ubuntu/workspace/app/channels/application_cable/connection.rb:7:in `connect'
Turns out it doesn't have access to the session, so you can't use that. http://edgeguides.rubyonrails.org/action_cable_overview.html#notes
You can probably do the signed cookies like the article there mentions as an alternative. That's how things work with Devise.
cookies.signed[:user_id]
instead but also has to change in your controllers as well.
Update: Here's an example on StackOverflow: http://stackoverflow.com/questions/37671504/getting-authlogic-to-work-with-actioncable
Thanks so much for the help, Chris!
In case anyone else wants to know, this is what i did:
1) I'm basically specificying which users id it is by storing a cookie of the current_user.id .
2) In application_controller.rb i have this with before_action :cookie_set and
def cookie_set
@user = current_user
return unless current_user
cookies[:user_name] = @user.id
end
3) and connection.rb , this will pull w/e is inside that user_name cookie.
def find_verified_user # this checks whether a user is authenticated with devise
if current_user = User.find(cookies[:user_name])
current_user
else
reject_unauthorized_connection
end
end
4) this is probably not secure since someone can change the cookie to any user. So i'll add a token:string to my users datatable to fix this later.
Masud!!! Thanks for this update. I was able to use this to get my action cable working on multi-tenancy... Thanks
Late to the conversation. I had the same issue originally. I just created a new symbol unique for actioncable.
connection.rb looks like
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags "ActionCable", "User #{current_user.id}"
end
protected
def find_verified_user
current_user = User.find_by(id: cookies.signed[:actioncable_user_id])
current_user || reject_unauthorized_connection
end
end
end
and in my sessions_helper.rb
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
#below is unique code added for ActionCable find_verified_user
cookies.signed[:actioncable_user_id] = user.id
end
end
The next thing I'm trying to figure out is how to use token based authentication.
Cookies and sessions isn't 100% safe and cross-domain happy (safari), so token based authentication may be really good. Also, you'll need to do this if you're going to build an iOS app to work.
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.email
end
protected
def find_verified_user # this checks whether a user is authenticated with devise
user_id = request.headers['x-user-id']
if user_id == nil
user_id = cookies[:user_id]
end
if current_user = User.find(user_id)
current_user
else
reject_unauthorized_connection
end
end
end
end
In application controller, you need to modify your current_user lines of code
def current_user
user_id = request.headers['x-user-id'] || cookies[:user_id]
@current_user ||= if user_id.present?
User.find_by(id: user_id)
else params[:token].present?
User.find_by(token: params[:token])
end
end