Wardenのwikiを翻訳してみた

Deviseのベースになっているgem "Warden"のwikiを翻訳してみました。(2022/12/18時点)
誤訳のご指摘があれば修正します。

# Warden
Warden wikiへようこそ。WardenはRackベースのRubyアプリケーションでの認証メカニズムを提供します。同じRackインスタンス内で複数のアプリケーションを共有することを念頭に作られています。
ここに長いドキュメントがあります。ドキュメントは最新を保つように努めますが、正確な詳細についてはソースコードやソースドキュメントを参照してください

## Overview
### The What
WardenはRubyWebアプリケーションの認証メカニズムを提供するために設計されたRackベースのミドルウェアです。Rackの構成要素に合う一般的なメカニズムで、認証用に強力な選択肢を提供します。
Wardenは怠惰に設計されています。つまり、使わないときには何もせず、使うときには突然動き出して、どんなRackベースのアプリケーションでも認証を許可する基本的メカニズムを提供するのです。

### The Why
Rackアプリケーションを使うようにすると機会が大きく広がります。同じプロセスやサブアプリケーション、そのまたサブアプリケーションで複数のアプリケーションを実行できるようになります。
複数からなるアプリケーションは多くの人にとって魅力的ですが、問題はどうやってこのような状況で認証を管理するかということです。それぞれのアプリケーションは認証、もしくは「ユーザー」を必要とすることがあります。全体として、Rackミドルウェアツリーの中にあるすべてのアプリケーションで、認証とはシステムへのログインを許可されている同じ「ユーザー」ということになりそうです。承認はさておき。
Wardenは下流のミドルウェアやエンドポイントが共通の認証メカニズムを共有しつつ、アプリケーション全体で管理することを可能にします。それぞれのアプリケーションはRackミドルウェアツリー全体を通して同じロジックを使い、認証されたユーザーにアクセスしたり、同じ方法で認証を要求することができます。それぞれのアプリケーションはどんなAPIでも上に重ねることができますし、それでいて基礎的なシステムは機能するのです。

### The How
WardenはRackスタックの中で、セッションミドルウェア(`env['rack.session']`にハッシュライクなオブジェクトであるセッションを格納するもの)の後に位置します。(注: RailsユーザーはRack環境にはrequest.envでアクセスできるので、`env = request.env`と考えてください。)
Wardenは遅延オブジェクトを`env['warden']`でRack環境に注入します。この遅延オブジェクトとのやりとりで、認証されているか問い合わせたり、Rackの構成要素のうち下流のマシンで認証させたりすることができます。もしリクエストが認証されていればWardenは邪魔をせず、そうでなければ"fail"させて反応させることができます。

```ruby
# リクエストが前に認証されたか問い合わせる
env['warden'].authenticated?

# リクエストが :foo スコープに対して認証されているか問い合わせる
env['warden'].authenticated?(:foo)

# :password ストラテジーによって認証を試みる。失敗しても続行する
env['warden'].authenticate(:password)

# :password ストラテジーによる認証を保証する。失敗したら脱出する
env['warden'].authenticate!(:password)
```

もしリクエストを認証したくないなら、`env['warden']`に認証するように頼まなければいいだけです。
認証が行われて成功したら、「ユーザー」オブジェクトにアクセスできるようになります。オブジェクトはnil以外なら何でもかまいません。
```ruby
env['warden'].authenticate(:password)
env['warden'].user # ユーザーオブジェクト
```

Wardenをセッションミドルウェアの直後に置くことで、すべての下流ミドルウェアとアプリケーションが認証オブジェクトにアクセスできるようになります。そうすると、別のソースから立ち寄られても、すべてのアプリケーションが認証への結合され一貫したアプローチを持つことができるのです。さまざまなAPIを重ねていても、すべてのRackミドルウェアとエンドポイントは基礎となる同じ認証システムを使うことができます。

「ストラテジー」は認証ロジックが実際に実行される場所です。詳細は[Strategies](https://github.com/wardencommunity/warden/wiki/Strategies)をご覧ください。

### Failing Authentication
認証が失敗しているようなら、Wadenミドルウェアの下流の任意のポイントで単に `:warden` シンボルを投げてください。また、オプションのハッシュに任意の情報を含めて投げることができます。
```ruby
throw(:warden)  # 認証失敗により脱出する
throw(:warden, :stuff => "foo")  # オプションで状況を示して脱出する
```
ここでしていることは"failure application"への脱出で、セットアップが必要です。標準的なRackアプリケーションの"failure application"は認証が失敗した場合に処理のためにあります。例えばログインフォームをレンダリングするために使うかことがあるでしょう。

### Callbacks
認証サイクルのキーポイントでたくさんのコールバックが定義されています。詳しくは[Callbacks](https://github.com/wardencommunity/warden/wiki/Callbacks)をご覧ください。

### Scopes
Wardenで複数のユーザーが同時にログインできるようになります。[Scopes](https://github.com/wardencommunity/warden/wiki/Scopes)をご覧ください。

## Setup
残念ながらWardenは少々セットアップが必要です。フレームワークでプラグインが使えるならいくらか楽にできます。

### Rack Setup
Wardenは何らかのセッションミドルウェアの下流にある必要があります。"failure application" を宣言しなければならず、デフォルトでどのストラテジーを使うかの宣言も必要です。
```ruby
Rack::Builder.new do
  use Rack::Session::Cookie, :secret => "replace this with some secret key"

  use Warden::Manager do |manager|
    manager.default_strategies :password, :basic
    manager.failure_app = BadAuthenticationEndsUpHere
  end

  run SomeApp
end
```

### Session Setup
何らかのオブジェクトをユーザーオブジェクトとして使うということは、Wardenにセッション内外でユーザーをシリアライズする方法を伝える必要があるということです。
以下の設定が必要です。
```ruby
Warden::Manager.serialize_into_session do |user|
  user.id
end

Warden::Manager.serialize_from_session do |id|
  User.get(id)
end
```
もちろん、必要なら複雑に設定することもできます。

### Declare Some Strategies
実際にWardenに認証させるにはいくつかストラテジーを宣言する必要があります。
詳しくは[Stratedies](https://github.com/wardencommunity/warden/wiki/Strategies)のページをご覧ください。

### Use it in your application
```ruby
env['warden'].authenticate!  # デフォルトの :password と :basic を使用
env['warden'].authenticate! :password #  :password ストラテジーのみ使用
```

### Advanced Setup (with scopes)
もしユーザーが1種類だけでスコープを使う必要がないなら、おそらくこのパートは飛ばした方がいいかもしれません。(もしくはあまり心配しすぎないでください。)

Wardenでは異なるスコープをセットアップして、さまざまなふるまいをさせることができます。これはとても強力な技術です。実際に例を見てもらったほうが早いでしょう。
```ruby
# 動作中のRailsアプリケーションから抽出
use Warden::Manager do |config|
  config.failure_app = ->(env) do
    env['REQUEST_METHOD'] = 'GET'
    SessionsController.action(:new).call(env)
  end
  config.default_scope = :user

  config.scope_defaults :user,        :strategies => [:password]
  config.scope_defaults :account,     :store => false,  :strategies => [:account_by_subdomain]
  config.scope_defaults :membership,  :store => false,  :strategies => [:membership, :account_owner]

  config.scope_defaults(
    :api,
    :strategies => [:api_token],
    :store      => false,
    :action     => "unauthenticated_api"
  )
end
```
ここではいろいろなことが行われています。
#### Default Scope
デフォルトではWardenはデフォルトスコープを設定しますが、上書きすることができます。
ここでは`:user`に上書きしています。これは`env['warden'].authenticate!` が`:user` スコープ内で認証することを意味しています(何も指定されていないので)。

#### Scope Defaults
それぞれのスコープはデフォルトのふるまいのリストと一緒に設定できます。ストラテジーのリスト、セッションで結果を永続化するかどうか、"Failure application"のアクションまでスコープごとに設定できます。
定義された方法でこれらのスコープのうちのどれかを使うには、そのスコープに対して認証するだけです。
```ruby
env['warden'].authenticate! :scope => :api
```
この呼び出しは`:api_token`ストラテジーによって認証を行い、セッションで永続化せず、その「ユーザー」を`:api`スコープで使えるようにします。(例)`env['warden'].user(:api)`

## Strategies
Wardenはリクエストを認証する必要があるかどうか判断するのにカスケードストラテジーの概念を用います。Wardenは以下のいずれかに行きつくまで次々にストラテジーを試していきます。
- ストラテジーが成功する
- 関連するストラテジーが見つからない
- ストラテジーが失敗する

### What is a Strategy
概念的にはストラテジーとはリクエスト認証用のロジックを置く場所のことです。実際には`Warden::Strategies::Base`の子孫にあたります。
多くのストラテジーを定義して選択的に使うことができます。定義を見てみましょう。
```ruby
Warden::Strategies.add(:password) do
  def valid?
    params['username'] || params['password']
  end

  def authenticate!
    u = User.authenticate(params['username'], params['password'])
    u.nil? ? fail!("Could not log in") : success!(u)
  end
end
```
ここでは`:password`ストラテジーを宣言しています。二点気を付けることとして、`#authenticate!`と`#valid?`メソッドがあります。
#### #valid?
`#valid?`メソッドはストラテジーのガードとして動作します。`#valid?`メソッドを宣言するかどうかは選択でき、宣言しない場合はストラテジーは常に実行されます。宣言しているならストラテジーは`#valid?`の評価がtrueの場合のみ試されます。
上記のストラテジーはパラメータに`'username'`か`'password'`のどちらかがあればユーザーがログインしようとしていると推測します。どちらかしかなければ`User.authenticate`の呼び出しは失敗しますが、望ましい(有効な)ストラテジーです。
#### #authenticate!
`#authenticate!`メソッドで実際にリクエストの認証ステップが行われます。リクエストを認証するためのロジックはここにあります。
リクエスト関連のたくさんのメソッドを使用できます。
- `request` - Rack::Request オブジェクト
- `session` - リクエスト用のセッションオブジェクト
- `params` - リクエストのパラメータ
- `env` - Rack env オブジェクト
ストラテジーで使えるアクションもたくさんあります。
- `halt!` - ストラテジーのカスケードを停止します。最後に処理されます。
- `pass` - ストラテジーを無視します。わかりやすくするためのもので、このアクションを呼び出す必要はありません。
- `success!` - ユーザーをログインさせるために`success!`にユーザーオブジェクトを渡します。`halt!`を発生させます。
- `fail!` - ストラテジーをfailにセットします. `halt!`を発生させます。
- `redirect!` - 別のURLにリダイレクトします。 `redirect!`にエンコードされるパラメータとオプションを渡せます。`halt!`を発生させます。
- `custom!` - カスタムのRack配列をそのまま返します。`halt!`を発生させます。
他にも
- `headers` - ストラテジーに関連するヘッダーをセットします。
- `errors` - エラーオブジェクトにアクセスできます。認証に関するエラーを入れることができます。

### Using Strategies
ストラテジーを使うには宣言時に設定したラベルで参照してください。
`:password`ストラテジーを使う場合:
```ruby
env['warden'].authenticate(:password)
```
複数のストラテジーを使うことができ、どれかひとつが停止するか何もなくなるまで一つずつ順番に試されます。
`:password`ストラテジーを使用し、失敗したら`:basic`ストラテジーを使う場合:
```ruby
env['warden'].authenticate(:password, :basic)
```

### Sharing Strategies
Wardenを使用している他のアプリケーションとストラテジーを共有することができます。この利点はもちろん個々の状況によります。
OpenID、Facebook、Googleのような柔軟に対応できるものにとってはストラテジーを共有することはおそらく役立つでしょう。しかし、このようなコミュニティの共有はあなたのニーズにぴったりフィットしないかもしれません。いいストラテジーが書かれれば、上手くいけば多くのストラテジーが知られるようになるでしょう。

ストラテジーの宣言は柔軟性を保ちながらも可能な限りシンプルにしました。
アプリケーションインスタンス間でストラテジーを共有することの最も明確な利益は、同じ会社に実行される複数のアプリケーションによるものです。多くの小さいアプリケーションは同じ認証を使うことができます。例えば、Sinatraや素のRackで簡単なテストアプリケーションを立ち上げたいとします。Wardenを使うとメインのアプリケーションから同じストラテジーを共有することができ、一貫した会社全体の認証要件を提供することができます。

## Failures
Wardenでレスポンスの認証に失敗すると、Rackエンドポイントが呼び出されます。このRackエンドポイントは"failure application"と呼ばれます。
ミドルウェアをスタックに追加する際に、認証失敗時に呼び出されるRackエンドポイントを提供する必要があります。

### Failing Authentication
認証を失敗させるにはシンプルに`:warden`シンボルを投げます。そのままシンボルを投げることもハッシュと投げることもできます。
```ruby
# failure applicationに脱出する:
throw(:warden)

# failure applicationに脱出して env['warden.options'] にオプションのハッシュを置く:
throw(:warden, :some => :option)
```
これはどんな下流のミドルウェアやエンドポイントからでも投げられます。
認証に失敗して`:warden`が投げられたら以下のことが起きます。
1. リダイレクトやカスタムRackレスポンスなどがないか遅延認証オブジェクトをチェックする。失敗しているか何も起きていない場合は"failure application"が呼び出される
2. `env['PATH_INFO']`が`"/unauthenticated"`に書き換えられる
3. 渡されたオプションは全て`env['warden.options']`に含まれる
4. 全ての`before_failure`コールバックが呼び出される
5. "failure application"が呼び出される

"failure application"で呼び出されるアクションを変えたい場合は、単純にthrowのオプションに`:action`シンボルを渡してください。以下のやり方があります。
```ruby
throw(:warden, :action => "different_action")

# 認証時
env['warden'].authenticate! :action => "different_action"
```
スコープごとに別の失敗時のアクションを投げるように設定できます。[Setup](https://github.com/wardencommunity/warden/wiki/Setup)をご覧ください。

## Users
Wardenの仕事は結局はリクエストを認証することです。それには2つの側面があります。
1. リクエストを送る人(ユーザー)
2. アプリケーション

### Accessing a user
ユーザーにアクセスするには、単に認証オブジェクトのユーザーを呼び出すだけです。
```ruby
env['warden'].user # 現在ログインしているユーザーを取得する
```
もしスコープ付きの認証を使用しているなら、どのスコープのユーザーにアクセスしたいのか伝える必要があります。
```ruby
env['warden'].user(:sudo)
```

### Setting the User
認証に成功したらユーザーが設定されます。手動でユーザーを設定したいときは、以下のように設定できます。
```ruby
env['warden'].set_user(@user)
```
スコープを使用しているとき:
```ruby
env['warden'].set_user(@user, :scope => :admin)
```
特定のリクエストでユーザーを設定して、セッションには保存したくない場合は`:store`をfalseに設定できます。
```ruby
env['warden'].set_user(@user, :store => false)
```

## Callbacks
認証サイクルの中ではさまざまなポイントで多くのコールバックを利用できます。
- after_set_user
- after_authentication
- after_fetch
- before_failure
- after_failed_fetch
- before_logout
- on_request

使いたい分だけコールバックは追加でき、宣言された順番に実行されます。先頭に追加したい場合は`prepend_before_failure`や`prepend_before_logout`のように、コールバック名の先頭に`"prepend_"`をつける必要があります。そして後述の同じ引数を渡します。

### after_set_user
ユーザーが設定されるたびに呼び出されます。ユーザーは
- `env['warden'].user`によって初めてアクセスされる各リクエストで
- ユーザーが最初に認証されたときに
- `set_user`メソッドで
設定されます。
#### 例
```ruby
Warden::Manager.after_set_user do |user, auth, opts|
  unless user.active?
    auth.logout
    throw(:warden, :message => "User not active")
  end
end
```

### after_authentication
ユーザーが認証されるたびに実行されます。(各セッションでの初回)
#### 例
```ruby
Warden::Manager.after_authentication do |user,auth,opts|
  user.last_login = Time.now
end
```

### before_failure
"failure application"が呼び出される直前に実行されます。
使用されるRackエンドポイントで必要な場合にenvを変更するのに役立ちます。例えば、エンドポイントが`request.params[:action]`をメソッド名に設定する必要がある場合です。
#### 例
```ruby
Warden::Manager.before_failure do |env, opts|
  request = Rack::Request.new(env)
  env['SCRIPT_INFO'] =~ /\/(.*)/
  request.params[:action] = $1
end
```

### before_logout
このコールバックは各ユーザーがログアウトする前に実行されます。ユーザーから`remember_me`トークンを削除する際に役立ちます。
#### 例
```ruby
Warden::Manager.before_logout do |user,auth,opts|
  user.forget_me!
  auth.response.delete_cookie "remember_token"
end
```

## Scopes
Wardenは複数のユーザーが同時にログインすることを可能にしますが、よく注意する必要があります。
Sudoアクセスや、パブリッシャーが他のユーザーとして閲覧するとどう見えるかをチェックすること、また、チェックアウトするときの安全な認証ステップなどを行う場合です。特定のアカウントへのユーザーのアクセスを認証するためにスコープを使用できます。
デフォルトではスコープは`:default`になっています。`:default`スコープは何もスコープが指定されていない時に使われます。

### Using Scopes
スコープはオブジェクトによって識別されます。(通常シンボルを使用します)
#### Authenticating
```ruby
# :sudo スコープのチェック
env['warden'].authenticated?(:sudo)

# :sudo スコープを :pgp ストラテジーで認証
env['warden'].authenticate(:pgp, :scope => :sudo)

# #authenticate と #authenticate! で同じオプションを使えます
```
#### Scoped User Access
```ruby
env['warden'].user(:sudo)
```
#### Logout
```ruby
env['warden'].logout  # セッションを消去。全ユーザーをログアウトさせる
env['warden'].logout(:default) # :default ユーザーをログアウトさせる
env['warden'].logout(:admin)  # :admin ユーザーをログアウトさせる
```

### Keeping Each User’s Data Separate
各ユーザーのデータを別々に保持するには[Authenticated Session Data](https://github.com/wardencommunity/warden/wiki/Authenticated-session-data)機能を使用します。

## Authenticated session data
Wardenのスコープは複数の認証済みユーザーが1つのセッション内に存在できるようにするメカニズムを提供します。
例として2つのスコープ、`:admin`と`:user`を使用する場合を考えてみましょう。`:user`スコープは一般ユーザーがログインする際にアプリケーションにアクセスするために使われます。`:admin`スコープは管理者ユーザーがログインする際に使われますが、実のところ認証ではなくセッションを整理する方法です。ストラテジーにおいてこれらのスコープの差異を決めるのはあなた自身ということです。
では、`:admin`ユーザーがログインして、特定のユーザーとしてサイトを閲覧するケースを考えてみましょう。adminユーザーが一般ユーザーのふりをしてサイトを訪問できるように、両方のユーザーを同じセッション内でログインさせることができます。
```ruby
warden = env['warden']
if warden.authenticated?(:admin)
  warden.authenticated?(:user) && warden.logout(:user)
  warden.set_user(@user, scope: :user)
end
```
今は`@user`としてログインしてサイトを訪問しています。デフォルトの`:user`スコープでサイトにいる間、何かをセッションに保存することがあります。
```ruby
env['warden'].session(:user)[:redirect_back] = "/some/url"
```
ここでは`{redirect_back: "/some/url"}`を保存します。このデータは`:user`スコープに指定されています。この技術を利用するために上記の「ユーザーのふり」の例を少し拡張します。
```ruby
warden = env['warden']
if warden.authenticated?(:admin)
  warden.authenticated?(:user) && warden.logout(:user)
  warden.session(:admin)[:redirect_back] = "/admin/path/to/somewhere"
  warden.set_user(@user, scope: :user)
end
```
これでセッション中に2つの`:redirect_back`キーを持っていることになります。1つがadmin用、もう1つが一般ユーザー用です。
```ruby
warden.session(:admin)[:redirect_back] # "/admin/path/to/somewhere"
warden.session(:user)[:redirect_back]  # "/some/url"
```
2セットのセッションデータはスコープ指定されていますが、同じセッション内に存在します。では、「ふり」をやめたらどうなるでしょうか。
```ruby
warden = env['warden']
warden.authenticated?(:admin) && warden.authenticated?(:user) # 両方のセッションを有効にする
warden.logout(:user) # 一般ユーザーのみログアウトし、セッションデータも一般ユーザーのものだけ削除する

redirect_to warden.session(:admin)[:redirect_back] || "/admin"
```
上記の例で一般ユーザーからログアウトすると、一般ユーザーはセッション全体から取り除かれますが(両方ログアウトしています)、スコープ指定されたセッションデータも消去されます。ただ、adminユーザーのスコープ指定されたデータだけはそのまま残ります。
全セッションからログアウトして、セッションデータも全て消去したい場合:
```ruby
env['warden'].logout
```
スコープが渡されていない場合は全ての既知のスコープがログアウトされ、データも消去されます。上記を呼び出す前に少なくとも
```ruby
env['warden'].authenticated?(scope)
```
をそれぞれのスコープに対して呼び出す必要があり、これによってWardenが各スコープを認識します。

## Testing
WardenはRackアプリケーションであるため、テストする際はフルスタックテストを行うことをお勧めします。Wardenに実際にテストに参加してもらうにはスタックを通す必要があります。
私はテストにはrack-testを使うことを好んでおり、バージョン0.9.5の時点でテストヘルパーをいくつか同梱しています。
例は全てRack::Testを想定しています。

### Requirements
ヘルパーを提供するにはアプリケーションのスコープに`Warden::Test::Helpers`をincludeしてください。
さらに重要なこととして、テストフレームワークのクリーンアップの段階で、Wardenをテストモードでは`Warden.test_reset!`を用いてリセットする必要があります。
```ruby
include Warden::Test::Helpers

after { Warden.test_reset! }
```
#### Login
```ruby
# テストスコープでincludeしてセットアップ
include Warden::Test::Helpers

# アクションの前に"A User"としてログイン
login_as "A User"
get "/foo"

# "An Admin"としてログイン
login_as "An Admin", :scope => :admin
get "/foo"

# adminと一般ユーザーの両方でログイン
login_as "A User"
login_as "An Admin", :scope => :admin
get "/foo"
```
#### Logout
```ruby
# ヘルパーのセットアップ
include Warden::Test::Helpers

# リクエストを受け取る前に全てのユーザーからログアウト
logout
get "/foo"

# リクエストを受け取る前にadminユーザーのみログアウト
logout :admin
get "/foo"
```

#### Custom
```ruby
# ヘルパーセットアップ
include Warden::Test::Helpers

# on_next_requestを追加するときはリクエストがWardenに到達したら実行される
# 一度到達したら処理は使い切られ以降のリクエストに影響しない
#
# 一般ユーザーとしてログインしている場合:
Warden.on_next_request do |proxy|
  proxy.set_user("Some User", :scope => :foo)
end
```