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?

Boris
  • 338
  • 2
  • 20
  • Have a look at Guest user creation... [as explained by the Devise Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user) – Myst Oct 17 '17 at 20:04
  • 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 Devise – Boris Oct 18 '17 at 08:11
  • @Boris Did you find any solution? I need this for my electron app – Osmond Mar 04 '18 at 18:11
  • @Osmond 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. – Boris Mar 06 '18 at 08:53

1 Answers1

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
Yazed Jamal
  • 168
  • 3
  • 8
  • 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`? – Boris Mar 12 '18 at 11:22
  • After reflection, I simply decided to assign `uuid` no matter if a Devise session exists or not. Accepting your answer, thanks for your help. – Boris Mar 12 '18 at 13:15
  • 1
    If you want an actual UUID you should be using `SecureRandom.uuid`. – siannopollo Apr 08 '18 at 18:36
  • 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. – Alexander Aug 06 '19 at 19:01
  • 2
    I noticed your solution never reaches the `reject_unauthorized_connection` command. Not a big deal if you are fine with accepting all connections. – hananamar Dec 04 '19 at 09:23