【Express】passport-localが動かない

# 環境

* Dockerコンテナ内で作業
* Node.js 18.15.0
* express 4.18.2
* passport 0.6.0
* passport-local 1.0.0(たぶん)
* express-session 1.17.3
* ビューエンジンにはpugを使用


# やりたかったこと

* ログインに関する処理は`routes/login.js`に書き、
`app.js`に書く内容は最低限にしたい。

* `/login`にGETでアクセスするとログインページが表示され、
そこにあるフォームにユーザー名とパスワードを入力すると、
`/login/auth`(authって何？)にPOSTリクエストが送られる

* `/login/auth`にPOSTでアクセスすると、passportによってログインができる

...のですが、色々なサイトの見様見真似でコードを書いていたら、
ログインページではログインができているのに、**それ以外のページだとログインできていない**現象が発生しました。


# やらかしたときのコード

もともとは以下のようなコードになっていました。
<sub>ただ、昔の記憶を掘り起こしながら書いたので、違うところがあるかもです。</sub>

```app.js
// 色々書いてある

// passport関連の記述はこれだけだったはず
app.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));

// 色々書いてある
```

```login.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');

passport.serializeUser((user, done) => {
    done(null, user);
});
passport.deserializeUser((obj, done) => {
    return done(null, obj.username);
});

// この部分はapp.jsと重複している(どっちに書けばいいのかわからなかったせい)
router.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));

router.use(passport.initialize());
router.use(passport.session());

passport.use(new LocalStrategy((username, password, done) => {
    done(null, { username, password });
}));

router.get('/', (req,res) => {
    res.render('login');
});

router.post('/auth', (req, res, next) => {
    passport.authenticate('local',
        {
            failureRedirect: '/failure',
            successRedirect: '/users'
        }
    )(req, res, next);
});

module.exports = router;
```


## このときの状況

上の記述だと、ログインページ(`/login`)ではログインできているけど、
それ以外のページだとログインできていませんでした。

このログインできていないというのは、`/login`以外のページで、

* `req.session`の中身が`undefined`
* `req.user`の中身も`undefined`
* **デシリアライズが行われていなかった**(`console.log`により検証)
* なんかpassportの検知対象外な気がする(予想)
* 一応それっぽいCookieはある

という感じです。

なので、例えばこんな感じになります。

```routes/login.js
// /loginにアクセスした時の挙動
router.get('/', (req, res) => {
    console.log(req.user); // ちゃんとユーザー名が出力される
    res.render('login');
});
```

```routes/users.js
// /usersにアクセスした時の挙動
router.get('/', (req, res) => {
    console.log(req.user); // undefinedが出力される
    res.send(`respond with a ${req.user}`); // 一生undefined
});
```


# 結論

おそらくですが、以下のコードは`app.js`に書くべきだったのだと思います。

```routes/login.js
app.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));

app.use(passport.initialize());
app.use(passport.session());
```

なので、上の部分のコードを`routes/login.js`から`app.js`に移動させると、
全てのページでログイン状態が見れるようになりました。

ただ、ここで一つ注意点があります。
上のコードをルーターを読み込むコードの前に書くと、passportが動かなくなります。

最終的には以下のようなコードになります。

```app.js
// 以上略(?)
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

// passportの記述はルーターより前に書く
app.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));
app.use(passport.initialize());
app.use(passport.session());

// ルーター
app.use('/', require('./routes/index'));
app.use('/users', require('./routes/users'));
app.use('/login', require('./routes/login'));

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  // 以下略
```

```routes/login.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.serializeUser((user, done) => {
    done(null, user);
});
passport.deserializeUser((obj, done) => {
    return done(null, obj.username);
});

passport.use(new LocalStrategy((username, password, done) => {
    done(null, { username, password }); // どんな文字でも通過
}));

router.get('/', (req,res) => {
    res.render('login');
});

router.post('/auth', (req, res, next) => {
    passport.authenticate('local',
        {
            failureRedirect: '/failure',
            successRedirect: '/users'
        }
    )(req, res, next);
});

module.exports = router;
```


# 実際に試したこと

:::note warn
ここから下に有益な情報はおそらくないと思われるので、
**読まなくて大丈夫です。**
試したことがなんの脈絡もなく書いてあるだけなので...
:::

上の結論に辿り着くまでに、いろいろ試してみました。


## とりあえずconsole.log

困った時はとりあえず`console.log`を書きまくる。
ということで、あちこちでログ出力をしてみました。

```diff_javascript:routes/login.js
const express = require('express');
const router = express.Router();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const session = require('express-session');

passport.serializeUser((user, done) => {
+   console.log('シリアライズ...', user);
    done(null, user);
});
passport.deserializeUser((obj, done) => {
+   console.log('デシリアライズ...', obj);
    return done(null, obj.username);
});

// この部分はapp.jsと重複している(どっちに書けばいいのかわからなかったせい)
router.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));

router.use(passport.initialize());
router.use(passport.session());

passport.use(new LocalStrategy((username, password, done) => {
+   console.log('ログイン判定', {username, password});
    done(null, { username, password }); // どんな文字でも通過
}));

router.get('/', (req,res) => {
+   console.log('ログインフォームにアクセス', req.user, req.session);
    res.render('login');
});

router.post('/auth', (req, res, next) => {
+   console.log('ログインページにPOSTアクセス', req.body);
    passport.authenticate('local',
        {
            failureRedirect: '/failure',
            successRedirect: '/users'
        }
    )(req, res, next);
});

module.exports = router;
```

```diff_javascript:routes/users.js
const express = require('express');
const router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
+ console.log('ユーザーページ', req.user, req.session);
  res.send(`respond with a ${req.user}`);
});

module.exports = router;
```

ここから下は各ページにアクセスした時の挙動です。

### /login

ログインしている時としていないときで出力が変わります。

:::note info
見づらかったためログを整形しています。
:::


#### 未ログイン状態の場合：

```
ログインフォームにアクセス 
undefined 
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true }
}
GET /login 304 466.917 ms - -
GET /stylesheets/style.css 304 6.229 ms - -
```

つまり、`req.user`は`undefined`で、`req.session`にはなんか入ってるみたいです。


#### ログイン状態の場合：

ちなみに、ユーザーネームとパスワードは共に`alice`でログインしています。
ログインは`/login`のログインフォームから行っています。

```console
ログインフォームにアクセス 
alice 
Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: { username: 'alice', password: 'alice' } }
}
```

まず、`req.user`には`alice`(ログインしているユーザー名)が入っています。

次に、`req.session`には、ログインしていない時と同じ`cookie`プロパティの他に、
なにやら`passport`というプロパティが入っています。
`passport`プロパティの中には、ログインしているユーザーの情報が入っています。

おそらく**この状態は正常**だと思われます。


### /login/auth

このページへはログインフォーム(`/login`)を使ってPOSTでアクセスしています。
リクエストのボデイの内容は`{username: "alice", password: "alice"}`です。

```
ログインページにPOSTアクセス [Object: null prototype] { username: 'alice', password: 'alice' }
ログイン判定 { username: 'alice', password: 'alice' }
シリアライズ... { username: 'alice', password: 'alice' }
POST /login/auth 302 61.880 ms - 56
ユーザーページ undefined undefined
GET /users 304 11.626 ms - -
```

ログインが成功したら`/users`にリダイレクトされるようになっているので、一部そのログが表示されています。
これはログインが成功している証拠でもあるのですが、いかんせん邪魔なので、
そのログを除いて整形するとこんな感じになります。

```
ログインページにPOSTアクセス 
[Object: null prototype] { username: 'alice', password: 'alice' }

ログイン判定 
{ username: 'alice', password: 'alice' }

シリアライズ... 
{ username: 'alice', password: 'alice' }

POST /login/auth 302 61.880 ms - 56
```

一番上の部分は`req.body`(リクエストボディ)を出力しています。
リクエストは意図した通りにできているみたいです。

次はユーザー名とパスワードがあっているかどうかチェックする部分で、
ログイン判定って書いてあるところです。
見た感じ、ユーザー名とパスワードはしっかり受け取れているみたいです。

その次はシリアライズが行われています。
こちらも成功しているように見えます。


### /users

次は`/users`にアクセスしてみました。
引き続き`alice`でログインしています。

```
ユーザーページ undefined undefined
GET /users 304 7.738 ms - -
```

...おや？
本来何か中身のある情報が欲しい箇所が`undefined`になっています。それも2つ。

まず、一つ目の`undefined`の箇所では、`req.user`を出力しています。
次に、二つ目の`undefined`の箇所では、`req.session`を出力しています。
つまり、どっちも空ってことです。

ちなみに、この直後に`/login`にアクセスしてみましたが、以下のようなログが出力されました。

```
デシリアライズ... { username: 'alice', password: 'alice' }
ログインフォームにアクセス alice Session {
  cookie: { path: '/', _expires: null, originalMaxAge: null, httpOnly: true },
  passport: { user: { username: 'alice', password: 'alice' } }
}
GET /login 304 396.308 ms - -
GET /stylesheets/style.css 304 5.134 ms - -
```

なにやらデシリアライズという処理が行われています。
ちなみに`/users`では行われていません。

`/login`以外のページはpassportの検知対象外みたいな感じか...?


## app.jsにコードを移動

ルーターにコードを書いているのが悪いのか...?と思い、
一度passport関連のコードを全てapp.jsに移動してみました。
そしたら**一切動かなくなりました**。

```app.js
// ルーター
app.use('/', require('./routes/index'));
app.use('/users', require('./routes/users'));
app.use('/login', require('./routes/login'));

// passport
app.use(session({
  secret: 'nanika',
  resave: false,
  saveUninitialized: false,
}));
app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((user, done) => {
  console.log('シリアライズ...', user);
  // 以下略
```

どうやらルーターより後にpassport部分の中の`app.use(session(`~`passport.session());`までを書くと動かないみたいです。
なので、該当の箇所をまでをルーターの記述の上に持っていきました。

```diff_javascript:app.js
+ // 初期化
+ app.use(session({
+   secret: 'nanika',
+   resave: false,
+   saveUninitialized: false,
+ }));
+
+ app.use(passport.initialize());
+ app.use(passport.session());

// ルーター
app.use('/', require('./routes/index'));
app.use('/users', require('./routes/users'));
app.use('/login', require('./routes/login'));

// passport

- // 初期化
- app.use(session({
-   secret: 'nanika',
-   resave: false,
-   saveUninitialized: false,
- }));
-
- app.use(passport.initialize());
- app.use(passport.session());

passport.serializeUser((user, done) => {
  // 以下略
```

そしたら無事に動きました。
よかった...


## app.jsは最小限に

`routes/login.js`に移せそうな箇所は移しました。
どうやら、上で移動した箇所以外は`app.js`以外の場所にあっても良さそうなので、
`routes/login.js`にごっそり移動してみました。

最終的に出来上がったのは上にある「結論」のコードなので、そっちを見てください。