Как получить current_user в приложении ActionCable rails-5-api?

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

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

  • Rails 5.0.1 --api (У меня НЕТ представлений и Я не использую кофе)
  • Я использую реактивное приложение, чтобы протестировать это (отлично работает БЕЗ авторизации)
  • Я НЕ использую devise для аутентификации (я использую 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>]

person Saravanabalagi Ramachandran    schedule 25.02.2017    source источник


Ответы (5)


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

#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 здесь в связи.

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

person Sajan    schedule 25.02.2017
comment
Я мало исследовал это, но пока я могу думать только об одном другом способе, т.е. об отправке параметра запроса, например, при использовании кабеля по умолчанию, предоставляемого rails, использовать App.cable = ActionCable.createConsumer('/cable?token=12345'); и использовать request.params[:token] в классе соединения. Но имейте в виду, что я не знаю, безопасно ли раскрытие такого токена или нет. Для меня подписанный файл cookie - лучший способ. Вы можете легко установить его после входа в систему. - person Sajan; 25.02.2017
comment
да, раскрывать токен таким образом небезопасно :/ Но это очень похоже на файл cookie, точно так же, как если кто-то получит файл cookie, он может украсть сеанс, точно так же, если кто-то получит строку jwt, он может аутентифицироваться с сервером до истечения срока его действия, как и файл cookie - person Saravanabalagi Ramachandran; 25.02.2017
comment
откуда cookies в cookies.signed[:user_id] объекте? Заголовки запроса? Если да, я тоже могу потянуть jwt ..! requestHeader['Authorization'] даст это... - person Saravanabalagi Ramachandran; 25.02.2017
comment
Я согласен, но украсть cookie немного сложнее, чем открытый токен. И здесь я имел в виду установить файл cookie только с user_id, сделать его подписанным файлом cookie, чтобы он был зашифрован. И при выходе очистите куки. - person Sajan; 25.02.2017
comment
Нет пользовательского заголовка. Каждый запрос отправляет файл cookie вместе с другими данными. Браузер отправляет файл cookie по умолчанию, поэтому, я думаю, вам не нужно устанавливать его вручную. Так что это просто рельсовый способ получить cookie. - person Sajan; 25.02.2017
comment
Спасибо @Sajan, за то, что подтолкнул меня к правильному пути... Я создал репозиторий action -cable-react-jwt для дальнейшей помощи в получении current_user с помощью JWT, особенно когда вы используете: Rails, React-Native и JWT. Поскольку запрос веб-сокета таинственным образом не позволял отправлять пользовательские заголовки, я просто сделал грязный хак для аутентификации JWT (добавив JWT в существующий заголовок: D) - person Saravanabalagi Ramachandran; 04.08.2018

если вы используете devise gems в рельсах, замените эту функцию:

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

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

person Sochetra Nov    schedule 17.07.2018

Ну в теории:

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

Итак, если вы знаете имя вашего сеансового файла cookie (как-то очевидно, пусть он называется «_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

person Not Entered    schedule 05.06.2019
comment
Это здорово! есть ли способ просто получить ключ cookie_store, чтобы нам не приходилось жестко кодировать имя файла cookie сеанса? - person rip747; 16.12.2019
comment
@rip747 Rails.application.config.session_options[:key] - person Not Entered; 06.01.2020

После установки self.current_user в ApplicationCable::Connection он становится доступным в экземплярах канала. Таким образом, вы можете настроить аутентификацию, как написал Саджан, и просто использовать current_user в MessageChannel.

Например, этот код работал для меня

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

    def connect
      self.verified_user = find_verified_user
    end

    private

    def current_user
      jwt = cookies.signed[:jwt]
      return unless jwt

      decoded = JwtDecodingService.new(jwt).decrypt!
      @current_user ||= User.find(decoded['sub']['user_id'])
    end

    def find_verified_user
      current_user || reject_unauthorized_connection
    end
  end
end

class NextFeaturedPlaylistChannel < ApplicationCable::Channel
  def subscribed
    stream_from "next_featured_playlist_#{verified_user.id}"
  end
end

person Axife    schedule 01.04.2020

Для режима API Rails 5:

application_controller.rb

class ApplicationController < ActionController::API
   include ActionController::Cookies
   ...
   token = request.headers["Authorization"].to_s
   user = User.find_by(authentication_token: token)
   cookies.signed[:user_id] = user.try(:id)

соединение.rb

class Connection < ActionCable::Connection::Base
   include ActionController::Cookies
   ...
   if cookies.signed[:user_id] && current_user = User.where(id: cookies.signed[:user_id]).last
     current_user
   else
     reject_unauthorized_connection
   end

config/application.rb

config.middleware.use ActionDispatch::Cookies
person Abel    schedule 17.01.2020