Send auth_token for authentication to ActionCable

后端 未结 10 1545
梦毁少年i
梦毁少年i 2021-02-04 02:23
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      #puts params[:auth_token]
      self         


        
相关标签:
10条回答
  • 2021-02-04 02:46

    It is also possible to pass the authentication token in the request headers and then validate the connection by accessing the request.headers hash. For example, if the authentication token were specified in a header called 'X-Auth-Token' and your User model have a field auth_token you could do:

    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.id
        end
    
        protected
    
        def find_verified_user
          if current_user = User.find_by(auth_token: request.headers['X-Auth-Token'])
            current_user
          else
            reject_unauthorized_connection
          end
        end
      end
    end
    
    0 讨论(0)
  • 2021-02-04 02:57

    I was asked about it recently and want to share the solution that I currently use in production systems.

    class MyChannel < ApplicationCable::Channel
      attr_accessor :current_user
    
      def subscribed
        authenticate_user!
      end
    
      private
    
      # this works, because it is actually sends via the ws(s) and not via the url <3
      def authenticate_user!
        @current_user ||= JWTHelper.new.decode_user params[:token]
    
        reject unless @current_user
      end
    end
    

    Then re-use warden strategies to work with that JWT (and let it handle all possible edge cases and pitfalls).

    class JWTHelper
      def decode_user(token)
        Warden::JWTAuth::UserDecoder.new.call token, :user, nil if token
      rescue JWT::DecodeError
        nil
      end
    
      def encode_user(user)
        Warden::JWTAuth::UserEncoder.new.call(user, :user, nil).first
      end
    end
    

    Though I didn't use ActionCable for the frontend it should roughly work like this:

    this.cable.subscriptions.create({
      channel: "MyChannel",
      token: "YOUR TOKEN HERE",
    }, //...
    
    0 讨论(0)
  • 2021-02-04 03:01

    Pierre's answer works. However, it's a good idea to be explicit about expecting these parameters in your application.

    For instance, in one of your config files (e.g. application.rb, development.rb, etc...) you can do this:

    config.action_cable.mount_path = '/cable/:token'
    

    And then simply access it from your Connection class with:

    request.params[:token]
    
    0 讨论(0)
  • 2021-02-04 03:05

    As for security of Pierre's answer: If you're using WSS protocol, which uses SSL for encryption, then the principles for sending secure data should the same as for HTTPS. When using SSL, query string parameters are encrypted as well as the body of the request. So if in HTTP APIs you're sending any kind of token through HTTPS and deem it secure, then it should be the same for WSS. Just remember that the same as for HTTPS, don't send credentials like password through query parameters, as the URL of the request could be logged on a server and thus stored with your password. Instead use things like tokens that are issued by the server.

    Also you can check this out (this basically describes something like JWT authentication + IP address verification): https://devcenter.heroku.com/articles/websocket-security#authentication-authorization.

    0 讨论(0)
  • 2021-02-04 03:06

    I managed to send my authentication token as a query parameter.

    When creating my consumer in my javascript app, I'm passing the token in the cable server URL like this:

    wss://myapp.com/cable?token=1234
    

    In my cable connection, I can get this token by accessing the request.params:

    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.name
        end
    
        protected:
        def find_verified_user
          if current_user = User.find_by(token: request.params[:token])
            current_user
          else
            reject_unauthorized_connection
          end
        end
      end
    end
    

    It's clearly not ideal, but I don't think you can send custom headers when creating the websocket.

    0 讨论(0)
  • 2021-02-04 03:06

    Another way (the way I did it in the end instead of my other answer) would be to have a authenticate action on your channel. I used this to determine the current user and set it in the connection/channel. All the stuff is send over websockets so credentials are not an issue here when we have it encrypted (i.e. wss).

    0 讨论(0)
提交回复
热议问题