0
0

【Express】passport-localが動かない

Last updated at Posted at 2023-11-28

環境

  • 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によってログインができる

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

やらかしたときのコード

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

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;

実際に試したこと

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

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

とりあえずconsole.log

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

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;
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

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

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

未ログイン状態の場合:

ログインフォームにアクセス 
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.userundefinedで、req.sessionにはなんか入ってるみたいです。

ログイン状態の場合:

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

ログインフォームにアクセス 
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());までを書くと動かないみたいです。
なので、該当の箇所をまでをルーターの記述の上に持っていきました。

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にごっそり移動してみました。

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

0
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
0
0