Skip to main content

connection.rb in actioncable chatroom series, if not using Devise for authentication?

Rails • Asked by Masud Hossain

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

http://stackoverflow.com/questions/32401697/how-does-warden-get-the-current-user-from-the-rack-session

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

Ok, my tabbing does not look like that. Don't judge me! lol


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

Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.