おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Railsでログイン状態を保持する方法

(6/20 大幅に加筆修正を行いました)Railsのセッションの有効期限は、デフォルトだとブラウザが終了するまでです。これだと、ログインページによくある「ログイン状態を保持する」チェックボックスが作れません。というわけで、やり方を調べてみたら、よさそうな情報がのっているサイトを見つけたので英語の勉強がてら概要を意訳してみたいと思います。

HowtoChangeSessionOptions in Ruby on Rails

すべてのセッションに対して一律に有効期限を作りたい場合

environment.rbに下記のように設定します。

ActionController::Base.session_options[:session_expires] = Time.local(2009,"jan")

ここでは、上記のように絶対時間を入れます。Time.now + 1.weekのようにするのはうまくないようです。なぜかというと、environment.rbが実行されるのはサーバが立ち上がる時のみなので、サーバ立ち上げ1週間後に発行するセッションは全て有効期限切れになってしまいます。例えば、6月20日にサーバを立ち上げた場合、もしそれが6月28日に発行されたセッションでも、有効期限は6月27日になります。つまりセッションが無効になってしまいます。


絶対的な時間ではなく、「セッションが発行されてから1週間」という設定にしたい場合は下記ページのプラグインを使うのがおすすめなよう。このプラグインを使うと、セッション発行日時からの相対的な時刻で有効期限を設定できます。
Dynamic session expiration times with Rails | Archives | codablog | Coda Hale

最終アクセス時刻から一定時間後を有効期限にしたい場合

下記の二種類の方法がありますが、両方やるのがおすすめらしいです。

  • セッションの有効期限を設定
  • サーバサイドでセッションの有効期限をチェックする

それぞれの設定方法を下記に示します。

セッションクッキーの有効期限を設定

先ほど紹介したプラグインを使う方法があります。

Dynamic session expiration times with Rails | Archives | codablog | Coda Hale

でもこのプラグインは全てのユーザのセッションに設定するものなので、「ログイン状態を保持する」チェックボックスのような、ユーザによって有効期限を変更するようなアプリケーションは作れません。ユーザによって有効期限を変更するようにするには、ApplicationControllerに下記のようなコードを書きます。

before_filter :setup

def setup
  if session[:user_id]
    if session[:persistent] # 「ログイン状態を保持する」チェックボックスをチェックしたらtrueになるようにしておく
      session.instance_variable_get(:@dbman).instance_variable_get(:@cookie_options)['expires'] = 3.months.from_now # ここが要所!
      session[:persistent] = Time.now 
    end

    # Fetch the user record.
    @user = User.find_by_id(session[:user_id], :readonly => true)
  end
end

session[:persistent]にTime.nowを入れているのは、常にセッションを更新するためです。(Railsはセッションが更新されたときだけブラウザにクッキーを送信する仕様なので、セッションを更新しないとせっかく有効期限を変更しても反映されない。)

上記方法の問題点

この章は訳ではなく、個人的な文です。


「ログイン状態を保持する」には2種類あると思っています。

  1. 初回ログインから一定期間で有効期限が切れる
  2. 最終ログインから一定期間で有効期限が切れる(つまり、期限内に再アクセスしたら有効期限が伸びる)

上記の方法で、2は実現できました。が、1は場合によってはうまくできないようです。ログイン処理時に下記の1行を入れればいいんじゃないかと思うでしょうが、なぜかうまく設定されない時があります。

session.instance_variable_get(:@dbman).instance_variable_get(:@cookie_options)['expires'] = 3.months.from_now

この件についてはいずれ調べたいと思います。

サーバサイドでセッションの有効期限をチェックする

セッションを使う時の問題点には下記のようなものがあります。

  • ユーザがクッキーをoffにしてるとき(これはセッション使うときには常につきまとう問題ですが・・・)
  • ユーザのPCの時計が進んでいて、発行と同時にセッション有効期限外になってしまうとき
  • ユーザ側でクッキーの有効期限をいじれてしまう

以上の理由により、サーバサイドでセッションの有効期限をチェックするのがよさそうです。例えばsession[:expiration]のようなセッションを、最初のセッション割当時に作成してチェックする方法があります。下記はコード例。*1

# The expiration time.
MAX_SESSION_TIME = 60 * 60

before_filter :prepare_session

def prepare_session

   if !session[:expiry_time].nil? and session[:expiry_time] < Time.now
      # Session has expired. Clear the current session.
      reset_session
   end

   # Assign a new expiry time, whether the session has expired or not.
   session[:expiry_time] = MAX_SESSION_TIME.seconds.from_now
   return true
end

サーバサイドでセッションの有効期限をチェックするその2

これは元記事には書いてなかったのですが。DBやファイルにセッションを格納している場合は、データの作成日や更新日を見て、一定期間が過ぎたものを削除するスクリプトを書けばOKだと思います。

*1:このコードはどうやら問題があるようです。がいまいち意味が分からなかったので、気になる方は元記事をあたってください。