環境
- 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によってログインができる
...のですが、色々なサイトの見様見真似でコードを書いていたら、
ログインページではログインができているのに、それ以外のページだとログインできていない現象が発生しました。
やらかしたときのコード
もともとは以下のようなコードになっていました。
ただ、昔の記憶を掘り起こしながら書いたので、違うところがあるかもです。
// 色々書いてある
// passport関連の記述はこれだけだったはず
app.use(session({
secret: 'nanika',
resave: false,
saveUninitialized: false,
}));
// 色々書いてある
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はある
という感じです。
なので、例えばこんな感じになります。
// /loginにアクセスした時の挙動
router.get('/', (req, res) => {
console.log(req.user); // ちゃんとユーザー名が出力される
res.render('login');
});
// /usersにアクセスした時の挙動
router.get('/', (req, res) => {
console.log(req.user); // undefinedが出力される
res.send(`respond with a ${req.user}`); // 一生undefined
});
結論
おそらくですが、以下のコードはapp.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.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) {
// 以下略
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
を書きまくる。
ということで、あちこちでログ出力をしてみました。
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;
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.user
はundefined
で、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.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.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
にごっそり移動してみました。
最終的に出来上がったのは上にある「結論」のコードなので、そっちを見てください。