1. Rails
    15 Feb 2016
    1. After upgrading an app to Rails 5.0.0.beta2 I started playing with Action Cable.
      In this post I want to show how to do authorization in Cable Channels.
      Whether you use CanCanCan, Pundit or whatever, first off you will have to authenticate the user, after that you can do your permission checks.

      How to do authentication is shown in the Action Cable Examples. Basically you are supposed to fetch the user_id from the cookie. The example shows how to check if the user is signed in and if not reject the websocket connection.
      If you need more granular checks, keep reading.

    2. To understand the following code you should first familiarize yourself with the basics of Action Cable, the Readme is a good start.

      The goal here is to identify logged in users and do permission checks per message. One could also check permissions during initiation of the connection or the subscription of a channel, the most granular option is to verify permissions for each message. This can be beneficial if multiple types of messages or messages regarding different resources which require distinct permissions are delivered from the same queue.
      Also imagine permissions change while a channel is subscribed, you would propably want to stop sending messages immediately if a user gets the permission to receive them revoked.

    3. In the ApplicationCable we define methods to get the user from the session and Cancancan’s Ability through which we can check permissions.

      module ApplicationCable
        class Connection < ActionCable::Connection::Base
          identified_by :current_user
      
          def connect
            self.current_user = find_verified_user
          end
      
          def session
            cookies.encrypted[Rails.application.config.session_options[:key]]
          end
      
          def ability
            @ability ||= Ability.new(current_user)
          end
      
          protected
          def find_verified_user
            User.find_by(id: session["user_id"])
          end
        end
      end

      We give Channel access to the session and the ability object. The current user is already accessable through current_user.

      module ApplicationCable
        class Channel < ActionCable::Channel::Base
          delegate :session, :ability, to: :connection
          # dont allow the clients to call those methods
          protected :session, :ability
        end
      end

      So far we setup everything we need to verify permissions in our own channels.
      So now we can use the ability object to deny subscription in general, or in this case to filter which messages are sent.

      Notice: Currently using ActiveRecord from inside a stream callback depletes the connection pool. I reported this issue under #23778: ActionCable can deplete AR’s connection pool. Therefore we have to ensure the connection is checked back into the pool ourselfs.

      class StreamUpdatesChannel < ApplicationCable::Channel
        def subscribed
          queue = "stream_updates:#{params[:stream_id]}"
          stream_from queue, -> (message) do
            ActiveRecord::Base.connection_pool.with_connection do
              if ability.can? :show, Stream.find(params[:stream_id])
                transmit ActiveSupport::JSON.decode(message), via: queue
              end
            end
          end
        end
      end
    1. 5 Responses

      leave a comment
    2. D41d8cd98f00b204e9800998ecf8427e?d=identicon
      Michael K. writes:
      29 Feb 10:14
      great stuff! I wondered how to process messages differently.
    3. F6c37c33188931ef56c5c1ccfe638dd5?d=identicon
      Yushun H. writes:
      09 Jun 23:10
      Does this works if there actioncable server is different from the client server? Specially the session method or cookie.
    4. 6c323b13b43d4f85aeb474a657f5ab94?d=identicon
      d writes:
      13 Jul 01:51
      Can you update it for Rails 5 release? How would the last part change (or anything else) now that Rails 5 is out of beta
    5. 8a2585a860d819a288e4657aec7bf9dd?d=identicon
      frexuz writes:
      19 Jul 09:22
      I'm not using CanCan, so I just removed the `ability` method. Then my `find_verified_user` (with Devise) becomes: `User.find_by(id: session['warden.user.user.key'][0][0])`. Works fine in Rails 5.0.0
    6. D41d8cd98f00b204e9800998ecf8427e?d=identicon
      writes:
      23 Jun 18:45
      Hello, I'm using my own Authentication code instead of using devise like gems. If I have to access the action cable via mobile platforms...is it the access_token (used for the authentication and identification of user)that I have to use instead of session cookies ?
    7. Leave a comment: