---
title: "Wardenのカスタム戦略の作り方：valid?・authenticate!とDevise連携・JWT認証"
url: "https://www.issoh.co.jp/tech/details/7166/"
published: 2025-06-10
updated: 2026-10-02
categories: ["Ruby on Rails"]
publisher: "株式会社一創"
---

# Wardenのカスタム戦略の作り方：valid?・authenticate!とDevise連携・JWT認証

Wardenは、Rackミドルウェアとして動くRubyの認証フレームワークです。認証の方法そのものは「戦略（strategy）」というクラスに書き、Wardenはどの戦略をどの順で試すか、成功したユーザーをセッションに残すか、失敗時に何を返すかを受け持ちます。Deviseもこの上に載っています。この記事では、Warden 1.2.9をRack 3.2.7とRuby 4.0.6で動かした結果をもとに、カスタム戦略の書き方とDeviseへの組み込み、テストまでを順に説明します。

## まとめ：カスタムWarden戦略を書くときの要点

- 戦略は`Warden::Strategies::Base`を継承し、`authenticate!`を定義して`Warden::Strategies.add`で登録します。`authenticate!`が無いと登録時に`NoMethodError`になります。
- `valid?`は「この戦略を試すかどうか」の判定です。認証情報の正否ではなく、ヘッダやパラメータが揃っているかだけを見ます。
- `fail!`は後続の戦略を止め、`fail`は次の戦略へ進めます。実測では`fail`にすると最後に失敗した戦略のメッセージが返りました。
- APIトークンやJWTの戦略は`store?`を`false`にします。実測ではSet-Cookieが付かず、次のリクエストはトークン無しで401になりました。ただし同じスコープに既存のログインセッションがあると、戦略そのものが実行されません。
- Deviseでは`config.warden`の中で`default_strategies(scope: :user).unshift`すると、Devise標準の戦略より先に自作の戦略が試されます。
- Warden 1.2.9の`redirect!`は`Location`を大文字で返すため、Rack 3の`Rack::Lint`で例外になります。Lintを挟む環境では`custom!`で小文字のヘッダを返します。

## Wardenの構成要素と2026年時点の版

Wardenを`use`すると、リクエストごとに`env["warden"]`へ`Warden::Proxy`が入ります。アプリ側はこのProxyの`authenticate!`や`user`を呼ぶだけで、どの戦略を使うかは設定と戦略側の`valid?`で決まります。

| 要素                | 役割                    |
| ----------------- | --------------------- |
| `Warden::Manager` | Rackミドルウェア本体・設定       |
| `Warden::Proxy`   | `env["warden"]`の実体    |
| 戦略（Strategy）      | 1つの認証方式の実装            |
| `failure_app`     | 認証失敗時のレスポンスを返すRackアプリ |
| シリアライザ            | ユーザーとセッション値の相互変換      |

RubyGems上の最新版は**1.2.9（2020年8月31日公開）**で、それ以降のリリースはありません。ただしリポジトリ（wardencommunity/warden）は動いており、Ruby 4.0をCIの対象に加える変更（[PR #222](https://github.com/wardencommunity/warden/pull/222)）が2026年9月4日にmasterへマージされています。依存はgemspecの`rack >= 2.0.9`だけで、Rack 3系でもそのまま入ります。Devise 5.0.4（2026年5月8日公開）は`warden ~> 1.2.3`に依存しているので、Deviseを入れたRailsアプリには必ずWardenも入っています。Ruby本体の版は[Rubyの最新バージョンとサポート期限](/tech/details/10592/)で確認してください。

## カスタム戦略の最小構成：valid?とauthenticate!

次の`config.ru`は、APIトークンとパスワードの2つの戦略を登録し、どちらかで認証できたら200、できなければ`failure_app`が401を返す最小構成です。`User`はトークン・ユーザー名・IDで検索できるモデルを想定しています。パスワード検証には`password_digest`と`has_secure_password`を用意します。Rack 3ではセッション用の`rack-session`も別途導入し、`SESSION_SECRET`には暗号学的に安全な乱数から生成した64バイト以上の秘密値を設定します。

```
# config.ru（Warden 1.2.9 / Rack 3.2.7 / Ruby 4.0.6 で実行）
require "warden"
require "rack/session"
require "json"

class ApiTokenStrategy < Warden::Strategies::Base
  # Authorizationヘッダが無いリクエストではこの戦略を飛ばす
  def valid?
    env["HTTP_AUTHORIZATION"].to_s.start_with?("Bearer ")
  end

  # APIトークンはリクエストごとに送られるのでセッションへ保存しない
  def store?
    false
  end

  def authenticate!
    token = env["HTTP_AUTHORIZATION"].delete_prefix("Bearer ")
    user = User.find_by_token(token)
    user ? success!(user) : fail!("invalid token")
  end
end

class PasswordStrategy < Warden::Strategies::Base
  def valid?
    params["username"] && params["password"]
  end

  def authenticate!
    user = User.find_by_name(params["username"])
    if user && user.authenticate(params["password"])
      success!(user)
    else
      fail!("invalid password")
    end
  end
end

Warden::Strategies.add(:api_token, ApiTokenStrategy)
Warden::Strategies.add(:password, PasswordStrategy)

failure_app = lambda do |env|
  opts = env["warden.options"]
  [401, { "content-type" => "application/json" },
   [JSON.generate(error: opts[:message], attempted: opts[:attempted_path])]]
end

use Rack::Session::Cookie, secret: ENV.fetch("SESSION_SECRET"), key: "_s"
use Warden::Manager do |manager|
  manager.default_strategies :api_token, :password
  manager.failure_app = failure_app
  manager.serialize_into_session { |user| user.id }
  manager.serialize_from_session { |id| User.find(id) }
end

run lambda { |env|
  user = env["warden"].authenticate!
  [200, { "content-type" => "application/json" }, [JSON.generate(user: user.name)]]
}
```

Rack::Testで送った結果は次のとおりです。

| リクエスト         | 応答                    | Set-Cookie |
| ------------- | --------------------- | ---------- |
| 正しいBearerトークン | 200                   | なし         |
| 認証情報なし        | 401（error: null）      | なし         |
| パスワード誤り       | 401（invalid password） | なし         |
| パスワード正解       | 200                   | あり         |
| 直後に認証情報なし     | 200（セッションから復元）        | あり         |

認証情報が無いときに`error`が`null`になるのは、どの戦略の`valid?`も`false`を返し、メッセージを設定した戦略が1つも無いためです。`failure_app`で「認証情報がありません」を出したいなら、メッセージが空のケースを自分で分岐させます。

登録時のチェックは`Warden::Strategies.add`のソースにあり、`authenticate!`が未定義なら`NoMethodError`、`Warden::Strategies::Base`の子孫でなければ`RuntimeError`になります。登録していない名前を`authenticate!(:nope)`のように渡すと、リクエスト時に`RuntimeError: Invalid strategy nope`で落ちます。

## 判定メソッドの使い分け：success!・fail!・fail・redirect!・custom!

`authenticate!`の中では、次のどれかを呼んで結果を決めます。「停止」は後続の戦略を試さないことを指します。

| メソッド                     | 結果        | 停止  | 主な用途     |
| ------------------------ | --------- | --- | -------- |
| `success!(user)`         | :success  | する  | 認証成功     |
| `fail!(msg)`             | :failure  | する  | 確定的な失敗   |
| `fail(msg)`              | :failure  | しない | 次の戦略に任せる |
| `pass`                   | 変更なし      | しない | 何もしない    |
| `redirect!(url)`         | :redirect | する  | 外部IdPへ送る |
| `custom!(rack_response)` | :custom   | する  | 応答を丸ごと指定 |

`fail!`や`redirect!`を呼んでもその場でレスポンスは返りません。戦略は結果を記録するだけで、`env["warden"].authenticate!`がユーザー不在を見て`throw(:warden)`し、Managerが`failure_app`や`redirect!`の内容を返します。`authenticate`（感嘆符なし）を使うと`throw`せず`nil`が返ります。

### fail!とfailの違い：後続戦略の停止と継続

パスワード戦略の後ろに、ユーザー名があれば必ず`fail!("fallback also failed")`する`:fallback`戦略を置き、パスワード誤りのリクエストを送りました。

```
# PasswordStrategy が fail! で失敗（次の :fallback は実行されない）
fail! calls=[:password]
401 {"error":"invalid password"}

# PasswordStrategy が fail で失敗（:fallback まで進み、最後の失敗メッセージが残る）
fail  calls=[:password, :fallback]
401 {"error":"fallback also failed"}
```

LDAPで失敗したらDBのパスワードを試す、という段階的な認証では`fail`を使います。逆に、トークンの署名が不正だった場合に別の戦略へ進ませると、意図しない方式で認証が通る余地が生まれます。トークン系の戦略は`fail!`で止めるのが安全です。

### 同一リクエスト・同一スコープでの戦略実行キャッシュ

同じリクエストの中で`authenticate!`を2回呼んでも、戦略は2回目には実行されません。Proxyは戦略のインスタンスをスコープごとにキャッシュし、`performed?`が真の戦略を飛ばします。実測でも呼び出し記録は`[:api_token]`の1件でした。コントローラのbefore\_actionとビューの両方で`authenticate!`を呼んでも、同じスコープで戦略キャッシュを保持している限り、戦略内のDB検索やJWT検証は再実行されません。別スコープでの認証や`clear_strategies_cache!`によるキャッシュ解除では、同じリクエストでも再実行されます。

### redirect!とRack 3：大文字ヘッダによるRack::Lintエラー

Warden 1.2.9の`redirect!`は、レスポンスヘッダを`headers["Location"]`と`headers["Content-Type"]`の大文字で組み立てます（2026年10月時点のmasterも同じです）。Rack 3はレスポンスヘッダ名を小文字に統一しており、`Rack::Lint`を挟むと次の例外になります。

```
Rack::Lint::LintError: uppercase character in header name: Location
```

rackup 2.3.1は`development`環境の既定ミドルウェアに`Rack::Lint`を含むため、素のRackアプリを`rackup`で開発していると当たります。Lintを外した状態では302と`location`が返り、Railsの`rails server`は既定ミドルウェアを空にしているので影響を受けません。Lintを残したい場合は、`custom!`で小文字のヘッダを持つレスポンスを返します。次のコードは`Rack::Lint`を挟んだ状態で302を返しました。

```
class SsoStrategy < Warden::Strategies::Base
  def valid? = params["sso"] == "1"

  def authenticate!
    url = "https://idp.example.com/authorize?" + Rack::Utils.build_query(client_id: "abc")
    custom!([302, { "location" => url, "content-type" => "text/plain" }, ["redirecting"]])
  end
end
```

## APIトークンとJWTの戦略：store?をfalseにする理由

`store?`の既定値は`true`で、成功したユーザーはセッションに保存されます。APIクライアントに毎回トークンを要求する場合、認証結果をセッションへ保存しない設定が必要です。前の章の実測でも、`store?`を`false`にしたトークン認証ではSet-Cookieが付かず、トークンを外した次のリクエストは401でした。ただし、`store?`を`false`にしても既存セッションの読み出しは止まりません。Wardenは同じスコープのユーザーをセッションから復元できると戦略を実行しないため、パスワード認証との共用スコープではトークンなしでも通ります。実測でも、パスワードでログインした同じセッションに不正なトークンを付けて送ると200が返りました。トークン必須のAPIは、セッション認証と分離したスコープ・構成で扱います。

JWTの戦略は次のように書けます。jwt gemの最新版は3.3.0（2026年9月11日公開）です。この例は前掲の固定APIトークン戦略とは別の認証方式です。登録後はJWT用の構成で`authenticate!(:jwt)`を呼ぶか、既定戦略を`:jwt`に設定します。前掲の`:api_token`を先頭に残すと、同じBearerヘッダを先に処理して`fail!`するため、JWT戦略まで進みません。

```
require "jwt" # jwt 3.3.0

class JwtStrategy < Warden::Strategies::Base
  def valid?
    env["HTTP_AUTHORIZATION"].to_s.start_with?("Bearer ey")
  end

  def store?
    false
  end

  def authenticate!
    token = env["HTTP_AUTHORIZATION"].delete_prefix("Bearer ")
    payload, = JWT.decode(token, ENV.fetch("JWT_SECRET"), true,
                          algorithm: "HS256", required_claims: ["exp", "sub"])
    user = User.find_by(id: payload["sub"])
    user ? success!(user) : fail!("unknown subject")
  rescue JWT::ExpiredSignature
    fail!("token expired")
  rescue JWT::DecodeError => e
    fail!("invalid token: #{e.class.name.split('::').last}")
  end
end

Warden::Strategies.add(:jwt, JwtStrategy)
```

同じ秘密鍵で5種類のトークンを作り、検証結果を確かめました。

| 送ったトークン  | 応答  | 捕捉した例外               |
| -------- | --- | -------------------- |
| 有効期限内    | 200 | なし                   |
| exp切れ    | 401 | ExpiredSignature     |
| expなし    | 401 | MissingRequiredClaim |
| alg=none | 401 | IncorrectAlgorithm   |
| 別の鍵で署名   | 401 | VerificationError    |

`algorithm: "HS256"`を明示しているので、署名なしの`alg=none`は受け付けません。`required_claims`を省くと`exp`の無いトークンが通り、有効期限による拒否が行われなくなります。JWTの構造や署名方式の選び方は[JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違い](/tech/details/13453/)、トークン寿命の決め方は[API認証の設計｜方式選定マトリクスとトークン寿命・監査ログの決め方](/tech/details/17232/)で扱っています。

固定のAPIトークンを照合する場合は、DBには平文でなくSHA-256などのハッシュを保存し、受け取ったトークンをハッシュしてから検索します。通常の文字列比較である`==`には、秘密値の比較に必要な定時間実行の保証がありません。パスワードは平文で保存・比較せず、専用のパスワードハッシュを保存します。Railsの`has_secure_password`を使う場合は、`user.authenticate`で入力値を検証します。

## Deviseにカスタム戦略を組み込む手順

Devise 5.0.4は、ルーティング読み込み後の`Devise.configure_warden!`で、モデルごとのスコープ（`:user`など）に標準の戦略を設定します。戦略の順序は、選択したモジュールを`Devise::ALL`順に整列し、対応する戦略を重複排除したうえで逆順にしたもので、`:database_authenticatable`と`:rememberable`を持つUserなら`[:rememberable, :database_authenticatable]`です。`config.warden`のブロックはこの設定の後に実行されるので、そこで先頭に自作の戦略を差し込めます。

```
# config/initializers/warden_api_token.rb
class ApiTokenAuthenticatable < Devise::Strategies::Base
  def valid?
    env["HTTP_AUTHORIZATION"].to_s.start_with?("Bearer ")
  end

  def store?
    false
  end

  def authenticate!
    token = env["HTTP_AUTHORIZATION"].delete_prefix("Bearer ")
    user = mapping.to.find_by(api_token_digest: Digest::SHA256.hexdigest(token))
    user ? success!(user) : fail!(:invalid_token)
  end
end

# config/initializers/devise.rb
Devise.setup do |config|
  config.warden do |manager|
    manager.strategies.add(:api_token, ApiTokenAuthenticatable)
    manager.default_strategies(scope: :user).unshift(:api_token)
  end
end
```

`default_strategies(scope: :user)`は設定済みの配列そのものを返すため、`unshift`で破壊的に先頭へ追加できます。Warden::Configで同じ操作をすると、結果は`[:api_token, :rememberable, :database_authenticatable]`になりました。戦略クラスを`config/initializers`に置いているのは、初期化の途中で読まれる設定からapp配下の再読み込み対象クラスを参照しないためです。

`Devise::Strategies::Base`を継承すると、スコープに対応するモデルを`mapping.to`で取れます。注意点が2つあります。

- Devise連携時は`ActionDispatch::Request`となり、`request.authorization`も使えます。素のWardenでは`request`が`Rack::Request`なので、Railsの`request.authorization`はありません（Rack 3.2.7に`authorization`メソッドは無い）。ヘッダは`env["HTTP_AUTHORIZATION"]`から読みます。
- `fail!`にシンボルを渡すと、Deviseの`FailureApp`が`devise.failure.invalid_token`のキーで翻訳します。キーが無ければ翻訳欠落の文言が出るので、ロケールに足しておきます。

```
# config/locales/devise.ja.yml（fail!(:invalid_token) の表示文言）
ja:
  devise:
    failure:
      invalid_token: "APIトークンが無効です。"
```

失敗時のレスポンスは`Devise::FailureApp`が決めます。`http_auth?`はXHRなら`Devise.http_authenticatable_on_xhr`、それ以外はリクエスト形式がナビゲーション形式（既定は`*/*`・`:html`・`:turbo_stream`）でなければ真になり、JSONのAPIには401、HTMLにはログイン画面へのリダイレクトが返ります。なお`Devise::Strategies::Base#store?`はCSRF検証に失敗したリクエスト（`env["devise.skip_storage"]`）でセッション保存を止めるための実装です。上の例のように`false`で上書きすると、その判定ごと不要になります。DeviseとSorceryの違いは[Sorcery gemでRailsに認証機能を実装する手順｜Deviseとの違い・最新0.18.0対応](/tech/details/4205/)、Rails APIでJWTを扱う全体像は[Rails APIとは？作成手順・JWT認証・React連携・Docker環境を実装解説【Rails 8.1】](/tech/details/2905/)を参照してください。

## スコープ・セッション・フックの扱い

スコープは「誰としてログインしているか」の区画で、`authenticate!(scope: :admin)`のように指定します。ユーザーはセッションの`warden.user.<スコープ>.key`に保存されるため、一般ユーザーと管理者を同じブラウザで同時にログインさせられます。`default_strategies`もスコープごとに分けられ、管理者だけパスワード＋ワンタイムコードの戦略にする、といった構成が取れます。

セッションまわりで押さえておく挙動は2つです。

- `set_user`はセッションに保存するとき、`rack.session.options`に`renew: true`を立てます。ログインのたびにセッションIDが振り直されるので、セッション固定化攻撃への対策は戦略側で書かなくて済みます。
- `logout`を引数なしで呼ぶとセッション全体をリセットし、`logout(:admin)`のようにスコープを渡すとそのスコープだけを消します。管理者画面からのログアウトで一般ユーザーまで落としたくない場合はスコープを渡します。

セッションIDの発行から失効までの一般的な設計は[セッション管理とは？セッションIDの発行から失効までの実装と保存先選定を解説](/tech/details/16366/)にまとめています。

### after\_set\_userによる停止済みアカウントのアクセス拒否

Wardenのフックは`after_set_user`・`after_authentication`・`after_fetch`・`before_failure`・`after_failed_fetch`・`before_logout`・`on_request`の7種類です。ログイン後にアカウントを停止されたユーザーを次のリクエストで締め出すには、セッションから復元したとき（`event: :fetch`）にも走る`after_set_user`を使います。

```
Warden::Manager.after_set_user except: :authentication do |user, auth, opts|
  if user.disabled?
    scope = opts[:scope]
    auth.logout(scope)
    throw(:warden, scope: scope, message: "account disabled")
  end
end
```

ログイン後にユーザーを停止状態へ変え、同じセッションで次のリクエストを送ると、401と`account disabled`が返りました。`except: :authentication`は、認証成功直後のイベントを検査対象から外す指定です。前掲の戦略には停止状態の判定がないため、このまま併用すると停止済みユーザーの初回認証は通ります。初回認証も拒否する構成では、このオプションを付けずにフックを登録します。

## カスタム戦略のテスト：単体テストとWarden::Test::Helpers

戦略はRackのenvを渡せば単体で生成できます。`Rack::MockRequest.env_for`でヘッダを組み立て、`authenticate!`を呼んで`result`・`user`・`message`・`halted?`を確かめます。次のMinitestは、前掲の`ApiTokenStrategy`を読み込み、`tok-alice`に対して名前が`alice`のユーザーを返す検証用モデルを用意した状態で、3件とも通りました。

```
require "minitest/autorun"

class ApiTokenStrategyTest < Minitest::Test
  def strategy_for(authorization)
    env = Rack::MockRequest.env_for("/me", "HTTP_AUTHORIZATION" => authorization)
    ApiTokenStrategy.new(env, :default)
  end

  def test_valid_token
    s = strategy_for("Bearer tok-alice")
    assert s.valid?
    s.authenticate!
    assert_equal :success, s.result
    assert_equal "alice", s.user.name
  end

  def test_invalid_token
    s = strategy_for("Bearer nope")
    s.authenticate!
    assert_equal :failure, s.result
    assert_equal "invalid token", s.message
    assert s.halted?
  end

  def test_skips_without_header
    refute strategy_for(nil).valid?
  end
end
# => 3 runs, 7 assertions, 0 failures, 0 errors, 0 skips
```

リクエストを通したテストでは、`Warden::Test::Helpers`の`login_as`で戦略を経由せずにログイン状態を作れます。

```
require "warden/test/helpers"
include Warden::Test::Helpers

login_as(user)              # 次のリクエストで :default スコープにログイン済みにする
login_as(admin, scope: :admin)
get "/me"                   # Rack::Test などでリクエストを送る

Warden.test_reset!          # 未実行の login_as を破棄する（teardown で呼ぶ）
```

`login_as`は「次のリクエストで`set_user`する」予約で、ユーザーはセッションに保存されます。`Warden.test_reset!`が消すのは未実行の予約だけで、Rack::TestのCookieに残ったセッションは消えません。実測でも`test_reset!`の後のリクエストは200のままでした。テスト間でログイン状態を切るには、Cookieを持つセッションごと作り直します。DeviseのIntegrationHelpersの`sign_in`は内部で`login_as`を呼んでいるので、同じ注意が当てはまります。RSpecでの書き方は[RSpecとは？Rubyのテスト自動化の書き方とrspec-rails導入・RSpec 4移行を解説](/tech/details/3508/)を参照してください。

## よくあるエラーと原因

| 症状                             | 主な原因                           |
| ------------------------------ | ------------------------------ |
| 常に401でメッセージがnull               | 全戦略のvalid?がfalse               |
| Invalid strategy xxx           | Strategies.addの登録漏れ            |
| No Failure App provided        | failure\_app未設定                |
| ログインが次のリクエストで消える               | store?がfalse／セッション層がWardenより内側 |
| LintError: uppercase character | redirect!とRack::Lintの組み合わせ     |
| Deviseで自作戦略が呼ばれない              | unshiftしたスコープ名の違い              |

セッションが保持されない問題は、ミドルウェアの順序が原因のことが多くあります。Wardenはセッションを読み書きするので、`Rack::Session::Cookie`などのセッション層を`Warden::Manager`より先に`use`します。順序が逆だと`env["rack.session"]`が無い状態でWardenが動きます。

## Wardenで戦略を書くべきでない場面

カスタム戦略を書くのは、既存のgemが用意していない認証方式を、既存のセッション管理やDeviseの仕組みに相乗りさせたい場合です。次の場面では自作しない方が保守の負担が軽くなります。

- **メールとパスワードだけの新規Railsアプリ**：Rails 8.0で追加された`bin/rails generate authentication`は、DBに保存するセッションとパスワードリセットのコードを生成します。生成コードはWardenを使っておらず（Rails 8.1.4のテンプレートに`warden`の語は0件）、全体を自分のコードとして読めます。
- **Google・GitHubなどのOAuth／OIDCログイン**：`redirect!`で自作するより、OmniAuthとDeviseのomniauthableを使う方が、stateパラメータやコールバックの検証を自分で書かずに済みます。
- **Deviseの標準機能で足りる要件**：ロック・確認メール・remember meはDeviseのモジュールにあります。自作の戦略で同じことをすると、Devise側の更新に追随する責任を自分で持つことになります。

逆に、社内の独自トークン、APIキー、リバースプロキシが付けるヘッダでの認証、LDAPとDBを順に試す構成は、戦略1つ分のコードで済むのでWardenの出番です。

## よくある質問

### Wardenとは何ですか？

Rackミドルウェアとして動くRubyの認証フレームワークです。認証方式は戦略クラスに書き、Wardenは戦略の実行順・セッションへの保存・失敗時の応答を受け持ちます。RailsでもSinatraでも素のRackアプリでも使えます。

### WardenとDeviseの違いは何ですか？

Wardenは認証の枠組みだけを持ち、ユーザーモデル・画面・ルーティングを持ちません。DeviseはWardenの上に、データベース認証・パスワードリセット・確認メールなどのモジュールとRails用のコントローラを載せたgemです。Devise 5.0.4は`warden ~> 1.2.3`に依存しています。

### Wardenは今もメンテナンスされていますか？

gemの最新版は2020年8月31日公開の1.2.9で、それ以降リリースはありません。リポジトリではRuby 4.0をCIに加える変更が2026年9月にマージされています。Rack 3系でも動きますが、`redirect!`のヘッダ名がRack::Lintに引っかかる点は残っています。

### fail!で渡したメッセージはどこで受け取れますか？

`failure_app`に渡るenvの`env["warden.options"][:message]`に入ります。試行したパスは`env["warden.options"][:attempted_path]`です。Deviseの`FailureApp`は、メッセージがシンボルの場合にi18nのキーとして翻訳し、文字列の場合はその文字列を使います。

### Rails 8の認証ジェネレーターはWardenを使っていますか？

使っていません。`bin/rails generate authentication`が生成するコードは、署名付きCookieに入れたセッションIDでDBのSessionを引く実装で、Wardenには依存しませんが、パスワードのハッシュ化には外部gemのbcryptを使います。

## 関連記事

- [Sorcery gemでRailsに認証機能を実装する手順｜Deviseとの違い・最新0.18.0対応](/tech/details/4205/)
- [Rails APIとは？作成手順・JWT認証・React連携・Docker環境を実装解説【Rails 8.1】](/tech/details/2905/)
- [JWTとは？構造・署名検証の仕組みとセッション・OAuth/OIDCとの違いを実装視点で解説](/tech/details/13453/)
- [セッション管理とは？セッションIDの発行から失効までの実装と保存先選定を解説](/tech/details/16366/)
- [API認証の設計｜方式選定マトリクスとトークン寿命・監査ログの決め方](/tech/details/17232/)

---

出典: [Wardenのカスタム戦略の作り方：valid?・authenticate!とDevise連携・JWT認証](<https://www.issoh.co.jp/tech/details/7166/>)（株式会社一創）
