Search code examples
ruby-on-railsauthenticationdeviseruby-on-rails-5actioncable

How To use devise_token_auth with ActionCable for authenticating user?


I have a Rails 5 API with devise_token_auth gem authentications.

Now I want personal chat for authenticated users. I do not have assets as I am using API and front is in native apps and I want native apps messaging.

So how I can authenticate users to use action cable for personal messaging using devise_token_auth gem


Solution

  • No cookies are generally supported in Rails 5 API. See: http://guides.rubyonrails.org/api_app.html#creating-a-new-application .

    If you do a common HTTP-authentification at first somewhere at your site ( with devise_token_auth gem), then you get 3 auth headers - access_token, client, uid.

    In such case you can use the Basic authentification for your Websockets connection (according https://devcenter.heroku.com/articles/websocket-security#authentication-authorization ) using these 3 auth headers:

    Call (I use Chrome Simple WebSocket Client):

    ws://localhost:3000/cable/?access-token=ZigtvvcKK7B7rsF_20bGHg&client=TccPxBrirWOO9k5fK4l_NA&uid=client1@example.com
    

    Then process:

    # Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading.
    module ApplicationCable
      class Connection < ActionCable::Connection::Base
        identified_by :current_user
    
        def connect
    
            params = request.query_parameters()
    
            access_token = params["access-token"]
            uid = params["uid"]
            client = params["client"]
    
            self.current_user = find_verified_user access_token, uid, client
            logger.add_tags 'ActionCable', current_user.email
        end
    
    
        protected
    
            def find_verified_user token, uid, client_id # this checks whether a user is authenticated with devise
    
                user = User.find_by email: uid
    # http://www.rubydoc.info/gems/devise_token_auth/0.1.38/DeviseTokenAuth%2FConcerns%2FUser:valid_token%3F
                if user && user.valid_token?(token, client_id)
                    user
                else
                    reject_unauthorized_connection
                end
            end   
      end
    end
    

    This is similar to the common Websockets auth https://rubytutorial.io/actioncable-devise-authentication/

    Such authentication is probably enough. I believe it is not necessary additionally to auth at the channel subscription and on every Websocket message sent to server:

    http://guides.rubyonrails.org/action_cable_overview.html#server-side-components-connections

    For every WebSocket accepted by the server, a connection object is instantiated. This object becomes the parent of all the channel subscriptions that are created from there on. The connection itself does not deal with any specific application logic beyond authentication and authorization.

    So if your connection is identified_by :current_user, you can later access current_user wherever inside your FooChannel < ApplicationCable::Channel! Example:

    class AppearanceChannel < ApplicationCable::Channel
      def subscribed
    
        stream_from "appearance_channel"
    
        if current_user
    
          ActionCable.server.broadcast "appearance_channel", { user: current_user.id, online: :on }
    
          current_user.online = true
    
          current_user.save!
    
        end
    
    
      end
    
      def unsubscribed
    
        if current_user
    
          # Any cleanup needed when channel is unsubscribed
          ActionCable.server.broadcast "appearance_channel", { user: current_user.id, online: :off }
    
          current_user.online = false
    
          current_user.save!      
    
        end
    
    
      end 
    
    end
    

    PS I you want to use cookies in Rails 5 API, you can switch it on:

    http://guides.rubyonrails.org/api_app.html#other-middleware

    config/application.rb

    config.middleware.use ActionDispatch::Cookies
    

    http://guides.rubyonrails.org/api_app.html#adding-other-modules

    controllers/api/application_controller.rb

    class Api::ApplicationController < ActionController::API
    
        include ActionController::Cookies
    ...