2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

passport.jsのいろいろ読む

Last updated at Posted at 2022-03-30

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

おことわり

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

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

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

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

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

主な関数

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

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

initialize

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

コメントと翻訳

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 アプリケーションによって使用されている必要があります。 アプリケーションが完全にステートレスである場合(セッションを使用しない)、このミドルウェアは必要ありません。しかし、その使用による悪影響はありません。

// デフォルト
// 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のバージョンが必要になる場合があり、その互換性を保つために使う必要があるっぽい。

コメントと翻訳

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()」ミドルウェアに同梱されているバージョンの関数で上書きすることが考えられます。 このオプションは、将来のメジャーバージョンリリースで再考されるべきものです。

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

use

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

コメントと翻訳 ソースコード内

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 のデフォルト名を上書きする。

// 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で見ることができる。
それぞれのストラテジ(すべてかは不明)には認証情報からユーザーを照合するverify関数を渡す。
verify関数にはストラテジごとに決められた引数が渡されてくるので、アプリ側でユーザーの照合(のやり方の指定)を行う。
ユーザーの照合が完了したらその結果をverify関数のdone関数にdone(error, user, info)の形で渡す。
エラーがない場合はerrorにはfalsenullを渡す。

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

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

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)を引数に取るミドルウェア関数(?)を返す。

コメントと翻訳

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

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

Authenticates requests.
Applies the nameed 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 レスポンスコードなどです。

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

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

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

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

    • successMessage, failureMessage
      booleanstringで指定する。
      ~~Messagereq.session.messages(配列)に追加されるメッセージを設定する。
      どちらも指定しない(false)とメッセージは追加されない。
      trueを指定するとverify関数のinfouse参照)に渡したメッセージが使用され、stringを渡した場合はそのメッセージを上書きできる。

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

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

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

コールバックについてのコメントと翻訳

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.

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

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

session

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

コメントと翻訳

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ヘッダで認証情報を提供する必要があります。

app.use(passport.session())

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

SessionStrategysessionという名前を持っているので

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

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

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

serializeUser

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

コメントと翻訳

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

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

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を実行しないと処理が止まってしまうので注意。
doneuserにはセッションに書き込みたい情報を渡し、エラーがある場合はerrorにエラー情報を渡す。
エラーがない場合は、第一引数errorfalsenullfalseとなる値)を渡し、userに書き込む値を渡せばいい。

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

authenticateのオプションのassignPropertycallbackが指定されていると実行されない。
(内部の話のlogIn参照)

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

deserializeUser

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

コメントと翻訳

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

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

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

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

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

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

「リクエストのセッション情報から認証を行う」はたぶん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.logInserializeUserが実行され、serializeUserで返された値がセッションへ書き込まれる。
    書き込む処理はpassport\lib\sessionmanager.jsにある。
    assignPropertycallbackを指定するとこのreq.logInが実行されなくなる。

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?