Using ActionCable with multiple identification methods
Asked Answered
C

1

9

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?

Condorcet answered 11/10, 2017 at 13:17 Comment(4)
Have a look at Guest user creation... as explained by the Devise WikiHonest
Thanks for your feedback, @Myst, unfortunately I cannot create (guest) users for every single websocket connection ... I will need to temporarily identify the connection by UUID without using DeviseCondorcet
@Condorcet Did you find any solution? I need this for my electron appSupen
@Supen Not a satisfying one, unfortunately. I moved the token creation for the anonymous channel to the subscribed method. It works, but it's not ideal.Condorcet
S
10

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
Stripling answered 10/3, 2018 at 8:9 Comment(5)
Thanks, I agree it makes sense to keep the logic in the connect method. In you solution, a user with an active Devise session would not have a uuid when using the anonymous channel. How would you deal with this, fallback to current_user.id?Condorcet
After reflection, I simply decided to assign uuid no matter if a Devise session exists or not. Accepting your answer, thanks for your help.Condorcet
If you want an actual UUID you should be using SecureRandom.uuid.Lat
Thank you very much for this answer! It was of critical help to me. For those that don't use devise, I stored the UUID in a cookie upon accessing a specific route and then used the cookie to authenticate the anonymous user. This worked great for my use case.Lobe
I noticed your solution never reaches the reject_unauthorized_connection command. Not a big deal if you are fine with accepting all connections.Train

© 2022 - 2024 — McMap. All rights reserved.