Send auth_token for authentication to ActionCable

后端 未结 10 1547
梦毁少年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 03:07

    Unfortunately for websocket connections, additional headers and custom ones are not supported1 by most2 websocket clients and servers. So the possible options are:

    • Attach as an URL parameter and parse it on the server

      path.to.api/cable?token=1234
      
      # and parse it like
      request.params[:token]
      

    Cons: It could be vulnerable as it may end up in logs and system process information available to others that have access to the server, more here

    Solution: Encrypt the token and attach it, so even if it can be seen in the logs, it would serve no purpose until its decrypted.

    • Attach JWT in one of the allowed parameters.

    Client side:

    # Append jwt to protocols
    new WebSocket(url, existing_protocols.concat(jwt))
    

    I created a JS library action-cable-react-jwt for React and React-Nativethat just does this. Feel free to use it.

    Server side:

    # get the user by 
    # self.current_user = find_verified_user
    
    def find_verified_user
      begin
        header_array = self.request.headers[:HTTP_SEC_WEBSOCKET_PROTOCOL].split(',')
        token = header_array[header_array.length-1]
        decoded_token = JWT.decode token, Rails.application.secrets.secret_key_base, true, { :algorithm => 'HS256' }
        if (current_user = User.find((decoded_token[0])['sub']))
          current_user
        else
          reject_unauthorized_connection
        end
      rescue
        reject_unauthorized_connection
      end
    end
    

    1 Most Websocket APIs (including Mozilla's) are just like the one below:

    The WebSocket constructor accepts one required and one optional parameter:

    WebSocket WebSocket(
      in DOMString url,
      in optional DOMString protocols
    );
    
    WebSocket WebSocket(
      in DOMString url,
      in optional DOMString[] protocols
    );
    

    url

    The URL to which to connect; this should be the URL to which the WebSocket server will respond.

    protocols Optional

    Either a single protocol string or an array of protocol strings. These strings are used to indicate sub-protocols, so that a single server can implement multiple WebSocket sub-protocols (for example, you might want one server to be able to handle different types of interactions depending on the specified protocol). If you don't specify a protocol string, an empty string is assumed.

    2 There are always excpetions, for instance, this node.js lib ws allows building custom headers, so you can use the usual Authorization: Bearer token header, and parse it on the server but both client and server should use ws.

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

    As I already stated in a comment the accepted answer is not a good idea, simply because the convention is that the URL should not contain such sensitive data. You can find more information here: https://tools.ietf.org/html/rfc6750#section-5.3 (though this is specifically about OAuth).

    There is however another approach: Use HTTP basic auth via the ws url. I found that most websocket clients allow you to implicitly set the headers by prepending the url with http basic auth like this: wss://user:pass@yourdomain.com/cable.

    This will add the Authorization header with a value of Basic .... In my case I was using devise with devise-jwt and simply implemented a strategy which inherited from the one provided in the gem which pulls the jwt out of the Authorization header. So I set the url like this: wss://TOKEN@host.com/cable which sets the header to this (pseudo): Basic base64("token:") and parse that in the strategy.

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

    In case any of you would like to use ActionCable.createCustomer. But have renewable token as I do:

    const consumer = ActionCable.createConsumer("/cable")
    const consumer_url = consumer.url
    Object.defineProperty(
      consumer, 
      'url', 
      {
          get: function() { 
            const token = localStorage.getItem('auth-token')
            const email = localStorage.getItem('auth-email')
            return consumer_url+"?email="+email+"&token="+token
          }
      });
    return consumer; 
    

    Then in case that the connection is lost it will be opened with a fresh new token.

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

    to add to previous answers, if you used your JWT as a param, you're going to have to at least btoa(your_token) @js and Base64.decode64(request.params[:token]) @rails as rails considers dot '.' a separator so your token will be cut off @rails params side

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