Как получить current_user в ActionCable rails-5-api app?

Почему мне не удается получить current_user внутри моего канала или как мне получить current_user?

Что я использую?

  • Rails 5.0.1 --api (у меня нет ни одного вида NOR, использующего кофе)
  • Я использую адаптивное приложение для тестирования этого (отлично работает БЕЗ авторизации)
  • Я НЕ использую devise для auth (вместо этого я использую JWT, используя Knock, поэтому нет файлов cookie)

Попытка получить current_user внутри моего канала ActionCable, как описано в rubydoc.info

Код выглядит как

class MessageChannel < ApplicationCable::Channel
  identified_by :current_user

  def subscribed
    stream_from 'message_' + find_current_user_privileges
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  protected

  def find_current_user_privileges
    if current_user.has_role? :admin
      'admin'
    else
      'user_' + current_user.id
    end
  end

end

И запустив его, я получаю эту ошибку:

[NoMethodError - undefined method `identified_by' for MessageChannel:Class]

И если я удалю identified_by :current_user, я получаю

[NameError - undefined local variable or method `current_user' for #<MessageChannel:0x7ace398>]

Ответ 1

Если вы видите предоставленный документ, вы узнаете, что identified_by не является методом для экземпляра Channel. Это метод для Actioncable::Connection. Из руководства Rails для обзора Actioncable это выглядит как класс Connection:

#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

    private
      def find_verified_user
        if current_user = User.find_by(id: cookies.signed[:user_id])
          current_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

Как вы можете видеть, current_user здесь недоступен. Вместо этого вам нужно создать current_user здесь в связи.

Сервер websocket не имеет сеанса, но он может читать те же файлы cookie, что и основное приложение. Поэтому, я думаю, вам нужно сохранить cookie после аутентификации.

Ответ 2

Спасибо @Sajan, за то, что кикнул меня по правильному пути...

Я создал репо action-cable-react-jwt, который точно делает именно то, что я задал в вопросе: Rails, React- Родной и JWT.

Поскольку запрос websocket таинственным образом не позволял отправлять настраиваемые заголовки, я просто сделал грязный взлом для аутентификации JWT (добавив JWT в существующий заголовок: D)

Ответ 3

если вы используете разработанные драгоценные камни в рельсах, пожалуйста, замените эту функцию:

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

Я надеюсь, что это поможет вам.

Ответ 4

Ну по идее

  • У вас есть доступ к файлам cookie в классе ActiveCable::Connection.
  • Вы можете устанавливать и получать cookies.signed и cookies.encrypted
  • И приложение, и ActionCable используют одну и ту же конфигурацию, поэтому они используют одну и ту же "secret_key_base"

Итак, если вы знаете имя вашего сеансового куки (как-то очевидно, пусть он будет называться " _session "), вы можете просто получить данные в нем:

cookies.encrypted['_session']

Итак, вы должны быть в состоянии сделать что-то вроде:

user_id = cookies.encrypted['_session']['user_id']

Это зависит от того, используете ли вы хранилище cookie для сеанса и точный подход к аутентификации, но в любом случае необходимые данные должны быть там.

Я нашел этот подход более удобным, поскольку сеанс уже управляется используемым вами решением для аутентификации, и вам, скорее всего, не нужно заботиться о таких вещах, как истечение срока действия файлов cookie и дублирование логики аутентификации.

Вот более полный пример:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      session = cookies.encrypted['_session']
      user_id = session['user_id'] if session.present?

      self.current_user = (user_id.present? && User.find_by(id: user_id))

      reject_unauthorized_connection unless current_user
    end
  end
end