Rails
ActionCable

【ActionCable】チャンネル接続/購読時にユーザ認証を行う

はじめに

ActionCableを利用して、チャンネル接続/購読時にユーザ認証を行う方法と、認証に成功/失敗した時にクライアント(JavaScript)側で呼び出されるfunctionについて確認した結果について投稿させていただきます。

投稿時のバージョンはRails 5.2.0です。

ActionCableの動作確認用に作成したアプリのソースコードはこちらに配置してあります。
NaokiIshimura/action-cable-example

前提条件

想定サービス

  • 複数のチャットルームが作成できるチャットサービス
  • ログインしないとサービスは利用できない
  • 許可されたユーザのみチャットルームに入室できる

クライアント(JavaScript)側の実装

app/assets/javascripts/channels/chat.js
var channel = "ChatChannel"; // チャンネル名
var room    = "1";           // チャットルームID

App.cable.subscriptions.create({channel: channel, room: room}, {
    // サブスクリプションがサーバー側で利用可能になると呼び出される
    connected: function () {
      //...
    },
    // WebSocket接続が閉じると呼び出される
    disconnected: function () {
      //...
    },
    // サブスクリプションがサーバーに拒否されると呼び出される
    rejected: function () {
      //...
    },
    //...
});

Cookieへのユーザ識別子(user_id)保存

Websocketサーバからはセッションにはアクセスできないが、Cookieにはアクセスできるため、ログイン成功時にCookieへuser_idを保存するようにしておく。

app/controllers/sessions_controller.rb
# Cookieにuser_idを署名付きで保存する
cookies.encrypted[:user_id] = user.id

Userモデルへのメソッド追加

ユーザがチャットルームへアクセスにアクセスできるか確認するメソッドをUserモデルに追記しておく。

app/models/user.rb
class User < ApplicationRecord

  def can_access?(room)
    if # チャットルームへ入室するための条件を記述
      true
    else
      false
    end
  end

end

ActionCableの実装

WebSocket接続時の認証

サンプルコードがAction Cable Overview — Ruby on Rails Guidesに紹介されていたので、そのまま利用した。

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

    protected
    def find_verified_user
      if current_user = User.find_by(id: cookies.encrypted[:user_id])
        current_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

チャンネル購読時の認証

サンプルコードがhttp://api.rubyonrails.org/|ActionCable::Channel::Baseに記載されていたので参考にした。

app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    room = Chat.find(params['room'])
    if current_user.can_access?(room)
      stream_from "messages_#{params['room']}_channel"
    else
      reject
    end
  end
end

結果

  • チャンネル接続/購読時の認証に成功するとjsでconnectedが呼び出される
  • チャンネル接続時の認証に失敗するとjsでdisconnectedが呼び出される
  • チャンネル接続時の認証に成功しても、購読時の認証に失敗するとjsでrejectedが呼び出される
チャンネル接続時の認証 チャンネル購読時の認証 jsで呼び出されるfunction
成功 成功 connected
成功 失敗 rejected
失敗 成功 disconnected
失敗 失敗 disconnected

補足

本投稿時点では英語版と日本語版のRailsガイドでサンプルコード内のCookieの保存方法に差分がある。
英語版では「暗号化Cookie(cookies.encrypted)」日本語版では「署名付きCookie(cookies.signed)」へユーザ識別子(user_id)を保存するようにしている。

参考