I'm trying to make current_user available in my channel actions.. This is the first time I'm diving into ActionCable and the tutorial I'm following is using Devise and I am not..
In channels/application_cable/connection.rb. They have
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
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
I'm having trouble with the protected method. I'm not sure how to verify the user from this file using bcrypt.. Also the directory this file is under seems to be likened to helpers for ActionCable, am I correct in thinking this?
Related
So I am trying to implement dervise-jwt in a Rails 5.2 application. My login is working properly, and the tokens are being created. That being said, I want to access that same current_user from my ActionCable. Following some online tutorials, my app/channels/application_cable/connection.rb looks like this:
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 user == env['warden'].user
user
else
reject_unauthorized_connection
end
end
end
end
When I debug the code, env['warden'].user is nil. What do I have to do to get the current_user accessible via warden?
From action cable documentation I read this example for setting current_user:
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 verified_user = User.find_by(id: cookies.signed[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
I don't have user_id cookie set because authentication is made for a cas server, and it store session in Active Record.
How tell to Connection class the current user?
You need to force the session to load. Check this link here (worked for me): How force that session is loaded?
I develop a Ruby on Rails 5.1 application using ActionCable. User authentification via Devise works fine for several channels. Now, I want to add a second type of channels which does not require any user authentification. More precisely, I would like to enable anonymous website visitors to chat with support staff.
My current implementation of ApplicationCable::Connection for authenticated users looks like this:
# 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
protected
def find_verified_user
user = User.find_by(id: cookies.signed['user.id'])
return user if user
fail 'User needs to be authenticated.'
end
end
end
Anonymous users will be identified by some random UUID (SecureRandom.urlsafe_base64).
Question:
How do I best add this new type of channels? Could I add a boolean flag require_authentification somewhere, override it in my inherited channel class for anonymous communication, and switch the identification method in Connection depending on this attribute? Or would I rather have to implement a completely new module, say AnonymousApplicationCable?
Hi I came into the same problem, after looking at your solution in rails github comment, I assume it is better to create the token and keep the logic in the connect method.
So what I do was just utillize the the warden checking and if it is nil just create the anonymous token and otherwise. For this to work, I need to declare 2 identifier :uuid and :current_user
class Connection < ActionCable::Connection::Base
identified_by :current_user, :uuid
def connect
if !env['warden'].user
self.uuid = SecureRandom.urlsafe_base64
else
self.current_user = find_verified_user
end
end
protected
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
end
Im following this guide to create Chatting feature with a use of Websockets.
https://www.sitepoint.com/rails-and-actioncable-adding-advanced-features/
Im stuck with a problem that env['warden'].user is retuning nothing even when Im loggined to the app with standard Devise form.
And if I use another method (which is commented now) - it return wrong user
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
verified_user = env['warden'].user
if verified_user
verified_user
else
reject_unauthorized_connection
end
end
# def find_verified_user
# user_id = request.headers['HTTP_AUTHORIZATION']
# if verified_user = User.find_by(user_id)
# verified_user
# else
# reject_unauthorized_connection
# end
# end
end
end
Logs says
Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2017-04-06 17:40:17 +0300
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
An unauthorized connection attempt was rejected
Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
I had the same problem, but it turned out my issue was because I had two Devise models: User and Customer, since User was used for admin stuff, Customer was set to Warden's default_scope. So to access the user scope, I had to do the following
env['warden'].user(:user)
The symbol at the end defines the scope to use.
Here's where I found information on Warden's Scoped Users: https://github.com/wardencommunity/warden/wiki/Scopes#scoped-user-access
I found solution on this article
https://rubytutorial.io/actioncable-devise-authentication/
Im not sure how it works, but it does the deal. How it would help for people with similar problem.
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
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
end
end
And I also created /config/initializers/warden_hooks.rb file
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"] = 60.minutes.from_now
end
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
Using Devise for authentication, I just ran into the same problem and the accepted answer didn't help me. My mistake was in my routes.rb file.
I placed: mount ActionCable.server, at: '/cable'
inside the authenticate :user do block. Moving it into the: Rails.application.routes.draw do block resolved the issue for me.
I made a basic chat with ActionCable authenticated with devise.
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
if verified_user = env['warden'].user
verified_user
else
reject_unauthorized_connection
end
end
end
end
But when the user has an open chat and it rejects the connection (because the users have logged out), I need to show a login screen.
The problem is that on the frontend I can't get the reason for the disconnection.
How can I send reject with params, like "unauthorized"?
def find_verified_user # this checks whether a user is authenticated with devise
if verified_user = env['warden'].user
verified_user
else
message = "The user is not found. Connection rejected."
logger.add_tags 'ActionCable', message # to console
self.transmit error: message # this is what you wanted
reject_unauthorized_connection
end
end
See also: How to terminate subscription to an actioncable channel from server?