世界が幸せで在ります様に

ITエンジニアになりたい人・エンジニアの人にとって役立ちそうな商品を紹介するブログ

Rails Devise利用&WebSocket(ActionCable)のログイン情報

基本的に、Railsにおいてログイン、セッション機能を使いたいときはDeviseを使う事が多い。今回は、それについてほんの少しだけ掘り下げてみる。

# Userモデルでdeviseを使う前提
User.find(user_id)

基本は、この形でDBからユーザ情報を引っ張ってくる。

環境
  • Ruby 2.2以上〜2.5系
  • Rails 4系以上〜5系

Deviseを使っていると、クッキーにセッションストア用のキーを持つ

Railsデフォルトでは、CookieStoreを利用するが、要はクッキーを利用する。クッキーは、4Kという制限があり、もしそれを超えるとクッキーがオーバーフローしてしまう。Rais3では、このクッキーが暗号化されなかったが、4からは暗号化されているため、アプリの秘密鍵を盗まれないと中身がバレルということには基本的にはならないはず。

セッションストア用の設定は以下に配置する。例えば、セッションキーの名前だったり、期限切れの日数を入れたりなど。

# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: 'valuablehatenablogcomID'

また、秘密鍵はどこにあるかというと、これもバージョンで少し異なる。Rails4では確か config/secrets.yml に環境ごとに設定しており、productionは別途生成して実際のサーバに保管というような形だった気がする。Rails5からは、 config/credentials.yml.enc に暗号化されている。

ログインが成功すると、セッションIDは古いものを破棄しないといけない。セッションIDを使いまわしていると、ハイジャックの恐れがある。

セッションハイジャック - Wikipedia

動かして学ぶセキュリティ入門講座 (Informatics&IDEA)

動かして学ぶセキュリティ入門講座 (Informatics&IDEA)

ただし、Deviseにはこの機能が備わっているので、あまり気にしなくても良い。

実際にログインしたあと

クッキーからセッション情報を取得してみる

session_key = Rails.application.config.session_options[:key]
cookies.encrypted[session_key]
# {"session_id"=>"880ed82eb7XXXXXXXXa5ec4bab33c391", "user_return_to"=>"/items/1", "warden.user.user.key"=>[[2], "$2a$10$wv442NE3XXXXXXXXIgD8nO"], "_csrf_token"=>"zmQpsSsB6eTjyQfn3XXXXXXXXmIUks2tveMp5qCKJCE="}

このように、セッション情報が取得できるので、ここからユーザ情報を取得できる。また、Deviseでは、モデル情報を元に様々な便利機能を定義しているし、実際はwardenという認証gemをベースにしている。

env['warden']
# Warden::Proxy:70000000046140 @config={:default_scope=>:user, :scope_defaults=>{}, :default_strategies=>{:user=>[:rememberable, :database_authenticatable]}, :intercept_401=>false, :failure_app=>#<Devise::Delegator:0x0ekd8e0d5247e8>}

先のwarden.user.user.keyからもwardenを利用していることが読み取れる。例えば、current_userの処理はこちらである。

devise/helpers.rb at master · plataformatec/devise · GitHub

def current_#{group_name}(favourite=nil)
              mappings = #{mappings}
              mappings.unshift mappings.delete(favourite.to_sym) if favourite
              mappings.each do |mapping|
                current = warden.authenticate(scope: mapping)
                return current if current
              end
              nil
end

ただし、websocketなどを使っているときは注意が必要

前提として、セッションが使えない。CookieStore使ってる。

ローカルと本番でそれぞれ構成が違う場合、クッキーを共存できない可能性もある。クッキーにユーザIDを入れたり、あるいは、セッションIDから取得するようにしたりとやり方はあり、ドメインが違っていて、ソレでもできないときはDOMにレンダリングするらしいやり方もあった。これは、大分変わったパターンではある。今回抜き出したのはActionCableのパターン。そもそもwebsocketからAPI呼び出しちゃう(セッション使える)というパターンも加えると4つ。

DOMにレンダリング

herenow.pw

セッションIDからユーザを取得する

stackoverflow.com

クッキーにユーザ識別子入れちゃう

qiita.com

作りはこんな感じだと思う。DOMレンダリングはちょっと内容が込み入ってるのでサイト見てもらったほうが早い。

# ログイン時
# クッキーに情報が入ってるなら、セッションキーから取得しても良いかも
cookies.encrypted[:user_id] = current_user.id

# actioncable連携時
# https://railsguides.jp/action_cable_overview.html#%E6%8E%A5%E7%B6%9A%E3%81%AE%E8%A8%AD%E5%AE%9A 引用
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user
 
    def connect
      self.current_user = find_verified_user
    end
 
    private
      def find_verified_user
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end
参考図書

WebSocket: Lightweight Client-Server Communications

WebSocket: Lightweight Client-Server Communications

WebRTC ブラウザベースのP2P技術

WebRTC ブラウザベースのP2P技術

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

ハイパフォーマンス ブラウザネットワーキング ―ネットワークアプリケーションのためのパフォーマンス最適化

TypeScriptネットワークプログラミング―HTML5/WebSocket/WebRTCによる

TypeScriptネットワークプログラミング―HTML5/WebSocket/WebRTCによる

プロフィールと免責事項