JMDC TECH BLOG

JMDCのエンジニアブログです

Devise のセッション有効期限と Timeoutable, Rememberable の設定次第で意図しない挙動になることについて

初めて JMDC TECH BLOG を書きました。 株式会社 JMDC で Pep Up 開発チームのバックエンドを担当している土橋と申します。

今年、JMDCではアドベントカレンダーに参加しています。 qiita.com

本記事は、JMDC Advent Calendar 202313日目の記事です。

弊社の『健康状態を可視化し、楽しみながら健康知識が身につくツール』Pep Up 、本記事を読んでくださっている方に利用されている方はいらっしゃいますでしょうか。

Pep Up は2016年に開始されたサービスで、当初からバックエンドに Ruby on Rails を採用しています。 認証には Rails で最もよく使われているであろう Devise を使用しています。

今回は、この Devise のモジュール、 Timeoutable, Rememberable を併用した際に設定次第では意図しない挙動になる可能性がある、ということについて例示してみます。

なお、この記事は Devise 4.9.3 を元にしています。

Devise とモジュール Timeoutable, Rememberable とは

Devise とは

devise logo

https://github.com/heartcombo/devise

Devise is a flexible authentication solution for Rails based on Warden.

Star 数も 23.5k と、 Rails の認証ライブラリとしては最大です。 Devise はモジュール式のアプローチを採っており、シンプルな認証機能だけではなく、標準で機能の追加が可能となっています。

Timeoutable と Rememberable

Devise の標準で用意されているモジュールとして、 Timeoutable と Rememberable というものがあります。 これらはログイン状態を制御することができるモジュールであり、Devise を使用している方は組み込んでいる方も多いのではないでしょうか。

まずは、この Timeoutable と Rememberable を設定した際、設定によってログイン状態がどのように変化するかについて記載したいと思います。

ログインしたときに発行される、認証情報が書き込まれた Cookie(以下「セッション ID クッキー」と呼びます)の有効期限(Expires)は、デフォルトでは Session (ブラウザを閉じるまで)となっています。

セッション ID クッキーの有効期限は、ブラウザの Cookie の Expires を指定することで実現されています。 セッション ID クッキーの有効期限は、以下のコードで設定することができます。

Rails.application.configure do
  config.session_store :cache_store, expire_after: 3.days
end

このコードの場合、3日間ログイン情報は保持され、ブラウザを閉じてもログイン状態は続く形になります。

このときのセッション ID クッキーの有効期限は、サイトにアクセスするたびに更新され、都度3日間の Expires が与えられることになります。

Timeoutable とは

Devise には Timeoutable というモジュールがあります。

https://github.com/heartcombo/devise/blob/e2242a95f3bb2e68ec0e9a064238ff7af6429545/lib/devise/models/timeoutable.rb#L7-L10

Timeoutable takes care of verifying whether a user session has already expired or not. When a session expires after the configured time, the user will be asked for credentials again, it means, they will be redirected to the sign in page.

Timeoutableはユーザーセッションがすでに期限切れになっているかどうかを確認します。設定された時間後にセッションの有効期限が切れると、ユーザーは再度クレデンシャルを要求され、サインインページにリダイレクトされます。

Timeoutable を使用すると、ユーザーセッションのタイムアウト時間を指定することができます。

Timeoutable は、ユーザーの未操作時間が指定時間に達した時点でユーザーをログアウトさせ、ログインページに戻すという動作をします。 Devise デフォルトの動作では、セッション ID クッキーの有効期限はブラウザを閉じるまでなので、ブラウザを閉じない限りいつまでも認証状態は続くのですが、 Timeoutable を設定することでユーザーが一定期間操作を行わなかった(サイトにアクセスしなかった)場合に、セッション ID クッキーを破棄しログアウトさせることができるようになります。

Timeoutable のデフォルト設定は30分となっていますので、30分操作しない状態になるとユーザーはログアウトされることになります。 また、セッション ID クッキーの有効期限が Session の場合は、ブラウザを閉じることによってもログアウトされます。

仮にセッション ID クッキーの有効期限を Session から変更していた場合は、ブラウザを閉じて開き直してもログイン状態は保持されていますが、Timeoutable の機能によって30分でログアウト状態になります。

Timeoutable の利点

単に30分でログアウトさせたいという要件だけであれば、セッション ID クッキーの有効期限を30分に設定することでも実現が可能です。

しかし、Timeoutable を設定しない状態だと、ユーザーが故意にセッション ID クッキーの有効期限を伸ばした場合など、そのセッション ID クッキーはいつまでも有効なセッション ID クッキーとして扱われてしまう恐れがあります。

これは、仮に何らかの経緯でセッション ID クッキーが漏れた場合、いつまでもそのセッション ID クッキーを使って認証状態にすることができてしまうということに繋がります。Timeoutable を設定していれば、サーバーサイドでアクセスのなかったセッション ID クッキーを無効にすることができますので、そういう意味でも Timeoutable は設定しておいたほうがより安全です。

また、ブラウザを閉じた時 or 一定期間が経過した時にログアウトさせる、という要件の場合は Timeoutable を使う必要があるでしょう。

Rememberable とは

もうひとつご紹介するモジュールは Rememberable になります。

https://github.com/heartcombo/devise/blob/e2242a95f3bb2e68ec0e9a064238ff7af6429545/lib/devise/models/rememberable.rb#L9-L14

Rememberable manages generating and clearing token for remembering the user from a saved cookie. Rememberable also has utility methods for dealing with serializing the user into the cookie and back from the cookie, trying to lookup the record based on the saved information.

Rememberable は、保存されたクッキーからユーザを記憶するためのトークンの生成とクリアを管理します。Rememberableはまた、ユーザをCookieにシリアライズし、Cookieから戻し、保存された情報に基づいてレコードを検索しようとすることを扱うためのユーティリティ・メソッドを持っています。

いわゆる「ログイン状態を保持する」のチェックボックスを実現するためのモジュールです。 Devise での認証時、 remember_me というフラグを ID やパスワードと同時に渡すことで、セッション ID クッキーの生存期間とは別にログイン状態を保持させることが可能となります。

デフォルトの期間は2週間が設定されます。

Rememberable を使うことで、例えばセッション ID クッキーの生存期間がデフォルトの Session だった場合であっても、ブラウザを閉じても2週間の間はログイン状態が保持されることになります。 この2週間という期間はログイン処理を行ってから2週間で、セッション ID クッキーのように延長されません。 延長したい場合は、 extend_remember_period という設定を行う必要があります。

Rememberable は、セッション ID クッキーとは異なった有効期限を持つ Cookie を発行することによってこの仕組みを実現しています。 この Cookie を以下 「Remember クッキー」と呼びます。

Remember クッキーが付与されるタイミングは、『ID, Password と共に remember_me のフラグがある状態で正常にログインされたタイミング』となります。

デフォルトでの2週間という期間は、この Remember クッキーの Expires のことを示しています。

仮にセッション ID クッキーの生存期間が Session の場合、ブラウザを閉じるとセッション ID クッキーは破棄されますが、セッション ID クッキーが無く Remember クッキーのみある状態でアクセスした場合、 Devise は自動的に再ログイン処理を行います。

Remember クッキーの延長タイミング

extend_remember_period というオプションを設定している場合、Remember クッキーの Expires は適宜延長されます。 この延長されるタイミングは『セッション ID クッキーがない状態でアクセスし、Remember クッキーによって再ログイン処理が行われたタイミング』となります。

ここで重要なポイントとして、 Rememberable が有効な状態では Timeoutable は動作しません

ログイン状態を保持しているので当然の動作ですが、Timeoutable は動作せず、30分を超えた状態になってもセッション ID クッキーは破棄されません。

本題

さて、ここからが本題です。

Devise のセッション有効期限と Timeoutable, Rememberable の設定次第で意図しない挙動になることについて

Devise のセッション有効期限と Timeoutable, Rememberable の設定次第で意図しない挙動になることがあります。

まず、例として以下のようなログイン要件があるものとします。

  • ブラウザを閉じてもログアウトさせずに、30分間はログイン状態を保持する
  • 未操作時間が30分経った時点で、ログアウトさせる
  • 「ログイン状態を保持」のチェックを付けたら、14日間ログイン状態を保持する
  • ログイン状態の保持は、最終アクセス時点で延長する

この要件を満たすには、以下のように設定します。

  • Rememberable を有効: 14日間
  • extend_remember_period を有効
  • Timeoutable を有効: 30分
Devise.setup do |config|
  config.remember_for = 14.days
  config.extend_remember_period = true
  config.timeout_in = 30.minutes
class User < ApplicationRecord
  devise :rememberable, :timeoutable

この設定で「ログイン状態を保持」のチェックがない場合は30分間、チェックを付けた場合は14日間ログイン状態が保持されることになります。

しかしこの時、何らかの理由で、セッション ID クッキーの有効期限の設定を Timeoutable より長い10日間に設定していたとします。

Rails.application.configure do
  config.session_store :cache_store, expire_after: 10.days

もちろんこの状態でも Timeoutable は有効ですので、Rememberable が有効ではない場合は30分でログアウトされセッション ID クッキーは破棄されます。

ログイン状態の保持が効かない?

しかし、このようにセッション ID クッキーの有効期限を長く設定してしまった場合、問題が発生します。

問題のない例

12/1 0:00に「ログイン状態を保持」にチェックを入れてログインしたユーザーが、セッション ID クッキーの無くなったあと、12/12 0:00に再アクセスしたとします。

セッション ID クッキーの有効期限は10日間としましたので、12/12時点では既にセッション ID クッキーは有効期限切れとなっていますが、Remember クッキーによって再ログイン処理が行われます。

セッション ID クッキーの期限が切れている場合は、Rememberable は自動的に再ログイン処理を行い、これにより Remember クッキーの更新も行われます。Remember クッキーの新たな有効期限は12/26となり、ユーザーのログイン状態もそこまで伸びます。

最終アクセス時点の12/12の14日後、12/26までログイン状態が保持されますので動作は問題ありません。

問題となる例

12/1 0:00に「ログイン状態を保持」にチェックを入れてログインしたユーザーが、セッション ID クッキーが破棄される前の12/10 0:00に再アクセスした場合はどうなるでしょうか。

Timeoutable の期限の30分は過ぎていますが、Rememberable が有効なので Timeoutable は動作せずログイン状態は保持されます。

さて、この時にセッション ID クッキーの状態はどうなるかというと…

一見 Timeoutable の30分は過ぎているのでセッション ID クッキーが破棄される…かのように思いますが、前述の通り Rememberable が有効な場合は Timeoutable は動作しません。

そして、セッション ID クッキーの期限は最終アクセス時点から延長されますので、12/10にアクセスした時点でセッション ID クッキーは12/20に期限が延長されます。

この際、セッション ID クッキーは有効なので Rememberable は再ログイン処理を行わず、Remember クッキーの延長は行われません。

これにより、セッション ID クッキーが12/20まで、Remember クッキーの有効期限が12/15までと期限が逆転する現象が発生します。

この状態になると、Remember クッキーの有効期限の15日までの間にアクセスしてもセッション ID クッキーが無くなっているタイミングが無いため、Remember クッキーが期限延長されることはありません。

仮に12/14 23:59にユーザーがアクセスしていたとしても Remember クッキーの期限は12/15 0:00から変化しません。

このまま、Remember クッキーの期限の12/15 0:00を迎えるとどうなるでしょうか?

Remember クッキーは破棄され、Rememberable は無効な状態となります。よって、Timeoutable の動作も復活します。

Timeoutable が動作するということは、ユーザーが30分以内にアクセスしていない場合にログアウトされますので、最短で12/15 0:30以降はユーザーはログアウト状態になります。

これが発生すると、要件にある「ログイン状態の保持は最終アクセス時点で延長する」が満たされていないように見えてしまいます。 ユーザーからしても『ログインを保持するチェックをしていたのに、突然ログアウトされた』と感じるでしょう。

まとめ

意図しない挙動を防ぐための対策

この問題は、セッション ID クッキーの有効期限を無駄に長くしてしまっていることに起因します。

Timeoutable を設定しているので、Timeoutable の期限以上にセッション ID クッキーの有効期限を伸ばす必要性は無いため、Timeoutable の有効期限と同じ長さにセッション ID クッキーの有効期限を設定するのが適切でしょう。

# config/environments/production.rb
Rails.application.configure do
  config.session_store :cache_store, expire_after: 30.minutes

Timeoutable, Rememberable のポイント

  • Rememberable の extend_remember_period はセッション ID クッキーが破棄されたタイミングで実行される
    • 最終アクセス時では無いことに注意
    • つまり最終アクセス時点から14日間有効にする、というのは厳密には実現できない(常にユーザーが29分ごとにアクセスしていたら Remember クッキーが延長されずそのうち切れる)
  • Rememberable が有効な場合 Timeoutable は動作しなくなる
    • Timeoutable のログアウト処理自体が行われなくなる

最後に

以上となります。意外にこのあたりの細かい挙動が書かれている記事が見当たらなかったため、今回書いてみました。

「当然知ってた」「内容が間違ってる!」と思われた方、「知らなかった!」という方も、ぜひ一度弊社のカジュアル面談にエントリーください! 一緒に働ける仲間を募集中です。 hrmos.co

明日14日目は、新保さんです!お楽しみに♪

JMDCでは、ヘルスケア領域の課題解決に一緒に取り組んでいただける方を積極採用中です!
フロントエンド /バックエンド/ データベースエンジニア等、様々なポジションで募集をしています。
詳細は下記の募集一覧からご確認ください。
hrmos.co

★最新記事のお知らせはぜひ X(Twitter)をご覧ください!
https://twitter.com/jmdc_tech