passport.jsのいろいろ読む

何しているのかわからなかったのでなんとなくドキュメントとかコメントとか読む。
読むのはパッケージの中身と公式のサンプルコード。

https://github.com/passport/todos-express-password

# おことわり
思っていたより大作（笑）になってしまいました。
**たぶんわかっている人からしたら基本的なことしか書いていません。**
**動かして確認しながら書いていますが、確認していない部分もあります。**

自分用メモ的要素が強いです。
読みにくい部分があるかもしれません。というかあります。

ところどころ間違っていたりニュアンスが違っているところがあるかもしれません。
また、バージョンによっても異なる可能性もあるので読む場合は鵜呑みにしないでください。

文中で言っている「コメント」とは、サンプルコード内とPassport本体のパッケージ内に記述されているコメントのこと。

ストラテジとは、Passportで使用する認証方法です。たぶん。
Twitter、Google、JWTなどいっぱいあって自分が使いたいストラテジを選んで使う。

# 主な関数

- `initialize`
  初期化の関数。
- `use`
  ストラテジを取り込む。
- `authenticate`
  使用するストラテジを指定する。
- `session`
  セッション情報での認証を行う。
- `serializeUser`
  ユーザー情報をセッションに保存する（方法を指定する）。
- `deserializeUser`
  セッション情報からユーザーを復元する（方法を指定する）。

コールバック関数として渡される`done`は基本的に実行しないと処理が止まってしまうので注意。

## `initialize`

Passportを初期化する関数らしい。
でもサンプルコードでは使われていない。謎。

<details><summary>コメントと翻訳</summary>

> Passport's primary initialization middleware.
This middleware must be in use by the Connect/Express application for Passport to operate.
>Options:
>  -  `userProperty`  Property to set on `req` upon login, defaults to _user_


> Passportの主要な初期化ミドルウェアです。
Passportが動作するためには、このミドルウェアがConnect/Expressアプリケーションによって使用されている必要があります。
>
>オプション:
> -  `userProperty`  ログイン時に "req" に設定するプロパティ、デフォルトは _user_ です。

> Intializes Passport for incoming requests, allowing authentication strategies to be applied.
If sessions are being utilized, applications must set up Passport with functions to serialize a user into and out of a session.  For example, a common pattern is to serialize just the user ID into the session (due to the fact that it is desirable to store the minimum amount of data in a session).
When a subsequent request arrives for the session, the full User object can be loaded from the database by ID.
Note that additional middleware is required to persist login state, so we must use the `connect.session()` middleware _before_ `passport.initialize()`.
If sessions are being used, this middleware must be in use by the Connect/Express application for Passport to operate.  If the application is entirely stateless (not using sessions), this middleware is not necessary, but its use will not have any adverse impact.

> 受信したリクエストに対してPassportを初期化し、認証戦略を適用することができるようにする。
セッションを使用する場合、アプリケーションは、セッションへのユーザーの入出力をシリアライズする機能をPassportにセットアップする必要があります。 たとえば、一般的なパターンは、ユーザーIDだけをセッションにシリアライズすることです（セッションに最小限のデータを保存することが望ましいという事実があるため）。
そのセッションに対して後続のリクエストが来たとき、完全なUserオブジェクトをIDでデータベースからロードすることができます。
ログイン状態を保持するためには、追加のミドルウェアが必要であることに注意してください。
セッションを使用する場合、Passport が動作するためには、このミドルウェアが Connect/Express アプリケーションによって使用されている必要があります。 アプリケーションが完全にステートレスである場合（セッションを使用しない）、このミドルウェアは必要ありません。しかし、その使用による悪影響はありません。

</details>


```js
// デフォルト
// req.userに情報が入る。
app.use(passport.initialize())
router.get('/', function(req, res, next) {
  res.render('index', { user: req.user})
})

// プロパティ指定
// req.currentUserに情報が入る。
app.use(passport.initialize({userProperty: 'currentUser'}))
router.get('/', function(req, res, next) {
  res.render('index', { user: req.currentUser})
})
```

デフォルトでは`req.user`にログインユーザーの情報が入っているが、`initialize`関数の`userProperty`オプションで名前を指定するとそこにユーザー情報が入れられる。

それ以外正直何をしているのかわかっていない。
使用するストラテジによっては以前のPassportのバージョンが必要になる場合があり、その互換性を保つために使う必要があるっぽい。

<details><summary>コメントと翻訳</summary>

>`passport@0.5.1` [removed][1] all internal use of `req._passport`.
From the standpoint of this package, this should have been a non-breaking change. However, some strategies (such as `passport-azure-ad`) depend directly on `passport@0.4.x` or earlier.
`require`-ing earlier versions of `passport` has the effect of monkeypatching `http.IncomingMessage` with `logIn`, `logOut`, `isAuthenticated` and `isUnauthenticated` functions that [expect][2] the `req._passport` property to exist.
Since pre-existing functions on `req` are given [preference][3], this results in [issues][4].
The changes here restore the expected properties needed when earlier versions of `passport` are `require`-ed.  This compatibility mode is enabled by default, and can be disabld by simply not `use`-ing `passport.initialize()` middleware or setting `compat: false` as an option to the middleware.
An alternative approach to addressing this issue would be to not preferentially use pre-existing functions on `req`, but rather always overwrite `req.logIn`, etc. with the versions of those functions shiped with `authenticate()` middleware.  This option should be reconsidered in a future major version release.

>"passport@0.5.1" [削除][1] "req._passport "のすべての内部使用を削除しました。
このパッケージの立場からすると、これは壊れることのない変更であったはずです。 しかし、いくつかの戦略("passport-azure-ad "など)は、`passport@0.4.x` 以前に直接 依存しています。 「passport」の以前のバージョンを "require "すると、「http.IncomingMessage」に「logIn」「logOut」「isAuthenticated」「isUnauthenticated」関数をモンキーパッチする効果があり、「req._passport」プロパティが存在すると [expectation][2] 期待されることになります。
req "上の既存の関数が[優先][3]されるため、[問題][4]が発生するのです。
今回の変更により、以前のバージョンの "passport "が "require "されたときに必要とされる期待されるプロパティが復元されました。 この互換モードはデフォルトで有効になっており、ミドルウェア「passport.initialize()」をuseしないか、ミドルウェアのオプションとして「compat: false」を設定するだけで解除することができる。
この問題を解決する方法として、「req」上で既存の関数を優先的に使用せず、「req.logIn」などを常に「authenticate()」ミドルウェアに同梱されているバージョンの関数で上書きすることが考えられます。 このオプションは、将来のメジャーバージョンリリースで再考されるべきものです。

</details>

compatはデフォルト有効だし`false`にしている例もないのでややこしいことしないならとりあえず使っとけ感。

## `use`

引数にストラテジを渡してPassportにストラテジを登録する。
名前の指定なしで実行するとストラテジのデフォルトの名前で登録されるが、第一引数に名前を指定することでストラテジの名前を上書きできる。

<details><summary>コメントと翻訳</summary>
ソースコード内

> The `LocalStrategy` authenticates users by verifying a username and password.
The strategy parses the username and password from the request and calls the `verify` function.
The `verify` function queries the database for the user record and verifies the password by hashing the password supplied by the user and comparing it to the hashed password stored in the database.  If the comparison succeeds, the user is authenticated; otherwise, not.

> LocalStrategyは、ユーザ名とパスワードを検証することでユーザを認証する。
ストラテジーはリクエストからユーザー名とパスワードを解析し、verify関数を呼び出します。
verify関数はデータベースにユーザーレコードを問い合わせ、ユーザーから与えられたパスワードをハッシュ化し、データベースに保存されているハッシュ化されたパスワードと比較することでパスワードを検証する。 比較に成功した場合はユーザが認証され、そうでない場合は認証されません。

パッケージ内

> Utilize the given `strategy` with optional `name`, overridding the strategy's default name.

> オプションの "name" で指定された "strategy" を利用し、strategy のデフォルト名を上書きする。

</details>

```js
// LocalStrategyデフォルトの'local'という名前で登録される。
passport.use(new LocalStrategy(...))
router.post('/login/password', passport.authenticate('local', {...}))

// 名前指定。'api'という名前で登録される。
passport.use('api', new LocalStrategy(...))
router.post('/login/password', passport.authenticate('api', {...}))
```

あくまで使う予定のストラテジの登録や設定をするだけで、`use`を実行するだけでは実際に使われるようになるわけではない。
実際に使う時は後述の`authenticate`を実行する。

ストラテジの引数(`new LocalStrategy(...)`の部分)にはストラテジごとにオプションがあり、Passport.jsの[Strategies](http://www.passportjs.org/packages/)で見ることができる。
それぞれのストラテジ(すべてかは不明)には認証情報からユーザーを照合する`verify`関数を渡す。
`verify`関数にはストラテジごとに決められた引数が渡されてくるので、アプリ側でユーザーの照合（のやり方の指定）を行う。
ユーザーの照合が完了したらその結果を`verify`関数の`done`関数に`done(error, user, info)`の形で渡す。
エラーがない場合は`error`には`false`や`null`を渡す。

`info`は成功、失敗時のメッセージの内容を、`string`か`{type: string, message: string}`の形で渡す。
`authenticate`使用時にオプションを指定しないとこちらで設定してもアプリ側でメッセージを拾えないっぽいので注意。（`authenticate`のオプションを参照）


公式サンプルコードではこの部分。（`done`関数は`cb`という名前で受け取っているので注意）

```js:routes/auth.js
passport.use('str', new LocalStrategy(function verify(username, password, cb) {
  db.get('SELECT rowid AS id, * FROM users WHERE username = ?', [ username ], function(err, row) {
    if (err) {
      return cb(err);
    }
    if (!row) {
      return cb(null, false, { message: 'Incorrect username or password.' })
    }
      
    crypto.pbkdf2(password, row.salt, 310000, 32, 'sha256', function(err, hashedPassword) {
      if (err) {
        return cb(err);
      }
      if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
        return cb(null, false, { message: 'Incorrect username or password.' });
      }
      return cb(null, row);
    });
  });
}));
```


## `authenticate`

ストラテジ名、オプション、コールバックを引数に取り、`(req, res, next)`を引数に取るミドルウェア関数（？）を返す。

<details><summary>コメントと翻訳</summary>

> Middleware that will authenticate a request using the given `strategy` name, with optional `options` and `callback`.

> 与えられた "strategy "名と、オプションの "options "および "callback "を使用してリクエストを認証するミドルウェアです。

> Authenticates requests.
Applies the `name`ed strategy (or strategies) to the incoming request, in order to authenticate the request.  If authentication is successful, the user will be logged in and populated at `req.user` and a session will be established by default.  If authentication fails, an unauthorized response will be sent.
>
>Options:
> - `session`          Save login state in session, defaults to _true_
> - `successRedirect`  After successful login, redirect to given URL
> - `successMessage`   True to store success message in req.session.messages, or a string to use as override message for success.
> - `successFlash`     True to flash success messages or a string to use as a flash message for success (overrides any from the strategy itself).
> - `failureRedirect`  After failed login, redirect to given URL
> - `failureMessage`   True to store failure message in req.session.messages, or a string to use as override message for failure.
> - `failureFlash`     True to flash failure messages or a string to use as a flash message for failures (overrides any from the strategy itself).
> - `assignProperty`   Assign the object provided by the verify callback to given property
>
> An optional `callback` can be supplied to allow the application to override the default manner in which authentication attempts are handled. The callback has the following signature, where `user` will be set to the authenticated user on a successful authentication attempt, or `false` otherwise.  An optional `info` argument will be passed, containing additional details provided by the strategy's verify callback - this could be information about a successful authentication or a challenge message for a failed authentication.
An optional `status` argument will be passed when authentication fails - this could be a HTTP response code for a remote authentication failure or similar.

> リクエストを認証する。
name "付きのストラテジーを受信したリクエストに適用し、 リクエストを認証する。 認証に成功した場合、ユーザーはログインし、"req.user "に入力され、デフォルトでセッションが確立されます。 認証に失敗した場合は、未承認の応答が送信されます。
>
> オプション
> - `session` ログイン状態をセッションに保存、デフォルトは _true_ です。
> - `successRedirect` ログインに成功したら、指定された URL にリダイレクトする。
> - `successMessage` req.session.messages に成功メッセージを格納する場合は True、成功メッセージのオーバーライドとして使用する場合は文字列を指定する。
> - `successFlash` 成功メッセージをフラッシュする場合はTrue、成功時のフラッシュメッセージとして使用する場合は文字列（ストラテジー自体からのオーバーライド）
> - `failureRedirect` ログインに失敗したら、指定したURLにリダイレクトする。
> - `failureMessage` req.session.messages に失敗時のメッセージを保存する場合は true、オーバーライドメッセージとして使用する場合は文字列を指定します。
> - `failureFlash` 失敗時のメッセージをフラッシュする場合はTrue、失敗時のフラッシュメッセージとして使用する場合は文字列を指定します（ストラテジー自体のメッセージをオーバーライドします）。
> - `assignProperty` 検証コールバックが提供するオブジェクトを、指定されたプロパティに割り当てます。
>
> オプションのcallbackを指定することで、認証の試行が処理される際のデフォルトの方法をアプリケーション側で上書きすることができます。このコールバックは次のようなシグネチャを持ちます。認証に成功すると「user」が認証されたユーザに設定され、そうでなければ「false」が設定されます。 オプションの "info" 引数が渡され、strategy の verify コールバックが提供する追加の詳細が格納されます。
認証に失敗した場合は、オプションの「status」引数が渡されます。これは、リモート認証に失敗した場合の HTTP レスポンスコードなどです。

</details>

```js
router.post('/login/password', passport.authenticate('local', {
  // オプション
}));
```

`use`で登録したストラテジ名を指定して認証を行う。

- ストラテジ名
 第一引数`name`で指定したストラテジを使用してユーザーを認証する。
ストラテジ名はストラテジごとに決められているのでそれを指定する。
`use`実行時に名前を上書きして登録している場合はそっちの名前で。
ストラテジ名は複数指定できるらしく、配列で渡すと順番に認証が試され、最初に認証できた認証方法で成功が返されるっぽい。（未検証）

- オプション
 ~~オプションはストラテジごとに共通っぽい。~~
ストラテジごとに追加のオプションがあったりする。
例えば`LocalStrategy`には`badRequestMessage`がある。
主に成功、失敗時の処理（成功時リダイレクト先など）についてのオプションを指定する。
詳しくは上の「コメントと翻訳」内のオプションを参照。  

  - `successMessage`, `failureMessage`
    `boolean`か`string`で指定する。
`~~Message`は`req.session.messages`（配列）に追加されるメッセージを設定する。
どちらも指定しない（`false`）とメッセージは追加されない。
`true`を指定すると`verify`関数の`info`（`use`参照）に渡したメッセージが使用され、`string`を渡した場合はそのメッセージを上書きできる。

  - `successFlash`, `failureFlash`
    **動作確認してません。**
`express-flash`を`app.use`で取り込んでおく必要があるっぽい。
`string`か`true`か`{type: string, message: string}`のオブジェクト型で指定する。
`flash('error')`か`flash('指定したtype')`で取り出すことができるらしい。
上書きの処理は`~~Message`と似ている。
`true`を指定した場合、`req.flash('error')`で`verify`関数の`info`に渡したメッセージが取り出すことができ、`string`を渡すとそのメッセージを上書き。
オブジェクト型で`type`を指定した場合は`flash('指定したtype')`で取り出す。

  `assignProperty`を指定した場合、認証のミドルウェアを通した以降は`req`の指定したプロパティ名でユーザー情報を参照できるようになるが、デフォルトのログイン処理が実行されなくなる。
ログイン処理がされないとは、認証自体は行われるが、セッションへの書き込みや認証後のリダイレクトなどが行われなくなるっぽい。
デフォルトのままでも`req.user`に入ってくるし、`initialize`の`userProperty`オプションでも変えられるのであまり使うことはなさそう。

- コールバック
コールバックはストラテジでデフォルトで定義されている認証後の処理を上書きできる。
成功時、失敗時の処理がすべて自分のアプリに任されることになる。（＝自分側で作ることになる）
コールバックを使わなくてもオプションの指定でだいたいは事足りると思うのでこちらもあまり使うことはなさそう。
責任を負いたくないのでここでは詳しくは書かない。


<details><summary>コールバックについてのコメントと翻訳</summary>

> An optional `callback` can be supplied to allow the application to override the default manner in which authentication attempts are handled.
The callback has the following signature, where `user` will be set to the authenticated user on a successful authentication attempt, or `false` otherwise.
An optional `info` argument will be passed, containing additional details provided by the strategy's verify callback - this could be information about a successful authentication or a challenge message for a failed authentication.
An optional `status` argument will be passed when authentication fails - this could be a HTTP response code for a remote authentication failure or similar.

> オプションの「コールバック」を指定することで、認証の試行が処理される際のデフォルトの方法をアプリケーション側で上書きすることができます。
このコールバックは次のようなシグネチャを持ちます。認証に成功すると「user」が認証されたユーザに設定され、そうでなければ「false」が設定されます。
オプションの "info" 引数が渡され、strategy の verify コールバックが提供する追加の詳細が格納されます。
認証に失敗した場合は、オプションの「status」引数が渡されます。これは、リモート認証に失敗した場合の HTTP レスポンスコードなどです。

> Note that if a callback is supplied, it becomes the application's responsibility to log-in the user, establish a session, and otherwise perform the desired operations.

> コールバックが提供された場合、アプリケーションの責任になることに注意してください。ユーザーをログインさせ、セッションを確立し、その他必要な操作を行う責任があります。

</details>


- ログイン後の処理
`authenticate`の後続の処理が実行されるかは`authenticate`の認証の結果（成功or失敗）と指定したオプションによって変わるので注意。
基本、リダイレクト系が設定されているかどうかによる。
ここで網羅して書くのは難しいので自分で調べてください。
調べたらあるかもだけど公式ドキュメントで見つけられませんでした。
コードは`passport\lib\middleware\authenticate.js`。

```js
passport.authenticate('local', {
  // ここのオプションで
}), function(req, res, next) {
  // ここが実行されるかどうかが変わる
});
```

## `session`
リクエストのセッション情報で認証を行う。

<details><summary>コメントと翻訳</summary>

> Middleware that will restore login state from a session.
Web applications typically use sessions to maintain login state between requests.  For example, a user will authenticate by entering credentials into a form which is submitted to the server.  If the credentials are valid, a login session is established by setting a cookie containing a session identifier in the user's web browser.  The web browser will send this cookie in subsequent requests to the server, allowing a session to be maintained.
If sessions are being utilized, and a login session has been established, this middleware will populate `req.user` with the current user.
Note that sessions are not strictly required for Passport to operate.
However, as a general rule, most web applications will make use of sessions.
An exception to this rule would be an API server, which expects each HTTP request to provide credentials in an Authorization header.

> セッションからログイン状態を復元するミドルウェア。
ウェブアプリケーションは通常、リクエスト間のログイン状態を維持するためにセッションを使用します。 例えば、ユーザーはサーバーに送信されるフォームに認証情報を入力することで認証されます。 認証情報が有効な場合、ユーザーのブラウザにセッション識別子を含むクッキーを設定することにより、ログインセッションが確立されます。 Web ブラウザは、その後のサーバーへのリクエストでこのクッキーを送信し、セッションを維持することができます。
セッションが利用され、ログインセッションが確立された場合、このミドルウェアは「req.user」に現在のユーザーを入力します。
Passport が動作するためにセッションは厳密には必要ないことに注意してください。
しかし、一般的なルールとして、ほとんどのWebアプリケーションはセッションを使用します。
このルールの例外は、APIサーバーで、各HTTP リクエストは、Authorizationヘッダで認証情報を提供する必要があります。

</details>

```js
app.use(passport.session())
```

Passport.js自体が内部に`SessionStrategy`というストラテジを持っており、`import`か`require`された時点で内部で`use`され、取り込まれた状態になっている。
`session`関数はそれを利用するためだけの特殊な`authenticate`関数とも言える。

`SessionStrategy`は`session`という名前を持っているので

```js
app.use(passport.authenticate('session'))
```

と処理は同じ。
というか`session`関数が内部でやっていることはこれ。

`SessionStrategy`は認証時に後述の`serializeUser`は実行されない。
おそらく「認証済みのユーザーがセッションに書き込まれている」という条件が前提なため。

## `serializeUser`
認証情報からセッションに書き込む情報を決める。

<details><summary>コメントと翻訳</summary>

> Registers a function used to serialize user objects into the session.

> ユーザーオブジェクトをセッションにシリアライズするために使用する関数を登録します。

</details>

```js
passport.serializeUser(function(user, done) {
  process.nextTick(function() {
    done(null, { id: user.id, username: user.username, test: 'aaaaa' });
  });
});
```

`authenticate`で認証が成功した時に、実行される。

第一引数`user`にはストラテジごとに決められた認証情報が渡される。
第二引数`done`にはコールバック関数が渡されており、`done(error, user)`の形で実行する必要がある。
`done`を実行しないと処理が止まってしまうので注意。
`done`の`user`にはセッションに書き込みたい情報を渡し、エラーがある場合は`error`にエラー情報を渡す。
エラーがない場合は、第一引数`error`に`false`や`null`（`false`となる値）を渡し、`user`に書き込む値を渡せばいい。

`error`と`user`を同時に渡して実行した場合は`error`が優先される。

`authenticate`のオプションの`assignProperty`か`callback`が指定されていると実行されない。
（内部の話の`logIn`参照）

`serializeUser`は複数登録でき、認証が成功した時に`serializeUser`で登録された順に実行される。
まず最初に実行した`serializeUser`が実行され、`done`関数が`error`と`user`両方`false`で実行された場合に、次の順番の`serializeUser`が実行される。
以降は同様に実行され、最初に`error`か`user`が`done`に渡された時点で終了。


## `deserializeUser`
セッションに書き込まれている情報からユーザーを復元する。

<details><summary>コメントと翻訳</summary>

> Registers a function used to deserialize user objects out of the session.

> セッションからユーザーオブジェクトをデシリアライズするために使用する関数を登録します。

</details>

```js
passport.deserializeUser(function(user, done) {
  process.nextTick(function() {
    return done(null, user);
  });
});
```

サンプルでは`return`しているがしなくてもいいっぽい。わからない。

`serializeUser`は書き込み版とするとこちらは復元版。
第一引数`user`にセッション情報、第二引数`done`にコールバック関数が渡される。
`done`は`serializeUser`同様に`done(error, user)`の形で実行する。
`done`の`user`に渡した情報が`req`内の`user`または`initialize`関数で指定した`userProperty`に入れられる。

`deserializeUser`も複数登録でき、登録順に実行されていき最初に`error`か`user`で返した情報が反映される。

「リクエストのセッション情報から認証を行う」はたぶん`SessionStrategy`しか行うことがないので`session()`か`authenticate('session')`していないと実行されないと思う。
少なくとも`LocalStrategy`では`deserializeUser`は実行されない。


# Passport内部の話
- strategy
`passport.use`で取り込まれたストラテジは`passport/authenticator.js`の`_strategies`オブジェクトに保持される。
`passport.authenticate(strategy_name)`で指定されたオブジェクトを`_strategies`から引き出して使用される。

- `logIn`
 `authenticate`ミドルウェアを通ると`req`の中に`logIn`が定義される。
つまり、セッション認証を使用するために`passport.session()`をアプリレベルで`app.use`しておくとすべてのルートで`req.logIn`が定義されることになる。
この`req.logIn`で`serializeUser`が実行され、`serializeUser`で返された値がセッションへ書き込まれる。
書き込む処理は`passport\lib\sessionmanager.js`にある。
`assignProperty`か`callback`を指定するとこの`req.logIn`が実行されなくなる。

- `serializeUser`, `deserializeUser`
 内部でそれぞれ`_serializers`、`_deserializers`という配列で保持されている。
`serializeUser`、`deserializeUser`を（第一引数に関数型を渡して）実行すると登録扱いでこれらの配列に追加されていく。
実行されるときはこの配列の`0`から順番に実行される。