LoginSignup
73

More than 3 years have passed since last update.

Passport.js の基本的な利用方法 (Node.js & Express)

Last updated at Posted at 2019-05-06

はじめに

本記事の目的

  • Node.jsで一般的に利用される認証ミドルである Passport.js の動作を理解すること
  • 公式サイトのドキュメントがわかりづらいので、Stepごとに理解できるように進めていく。

Passportの動作の基本的な概念

  • まずPassportをミドルウェアとして動作させる場合、HTTPリクエストの body にしかるべきラベルで IDとパスワードを入れてやる必要がある。デフォルトでは以下の通り。
    • body: { username: 'andre', password: '12345' }
    • これが仮に { name: 'andre', pass: '12345'} のようになっていると、デフォルトでは動作しない。
  • LocalStrategyのコールバック関数は、done()のコールバックを返すようにしていれば実装方法は任意でよく、とにかく与えられたIDとパスワードが正しいか(IDがある、パスワードが正しい、など)判断し、done()を返せばいい。
  • 一般的に app.postでのミドルウェアの例が多いが、router.postでのミドルウェアとしても動作する。

前提環境

ソフトウェア バージョン
Node.js v10.15.3
express 4.16.4
Passport 0.4.0

その1. 最小限の実装

  • 極小の実装(セッション非利用、ユーザデータは埋め込み)で作成。
  • app.postではなく、router.postでやっている。
  • Passportに渡すパラメータが正しいか確認するため、リクエストボディをコンソールに表示する。

挙動の解説

  • express から、http://localhost:3000/ へのリクエストを indexルータ で処理。
  • passportにパラメータを渡すため、app.use(express.urlencoded( { extended: false }))が必要。
  • 認証が成功した場合、passportミドルウェアはリクエストヘッダにreq.userをセットし、次のコールバックに処理を渡す。
  • 動作を確認するのが目的なので、認証失敗時のページは実装していない。

実装

index.js
const express = require('express');
const router = express.Router();

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const User1 = {
  name: "Taro",
  password: "Taro123"
};


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

    if(username !== User1.name){
      // Error
      return done(null, false);
    } else if(password !== User1.password) {
      // Error
      return done(null, false);
    } else {
      // Success and return user information.
      return done(null, { username: username, password: password});
    }
  }
));

router.use(passport.initialize());


router.get('/', (req, res) => res.render('index'));

router.post('/',
  (req, res, next) => {
    console.log(req);
    next();
  },
  passport.authenticate('local',
    {
      session: false,
      failureRedirect : '/failure'
    }
  ),
  (req, res) => {
    console.log(req.user);
    res.send('Success');
  }
);

module.exports = router;
  • 参考に app.js と index.hbs も掲載する。
app.js
const express = require('express');
const app = express();

app.set('view engine', 'hbs');

app.use(express.urlencoded( { extended: false }));
app.use('/', require('./routes/index'));


app.listen(3000, console.log('Server listening port 3000...'));
index.hbs
<!DOCTYPE html>
<body>
  <form action="/" method="post">
    <input type="text" name="username" id="">
    <input type="password" name="password" id="">
    <input type="submit" value="Try Auth">
  </form>
</body>
</html>

以上となる。

その2. セッションを利用する

  • 次に、express-sessionを導入し一般的な利用方法に近づける。

動作概念

  • Passport は認証が成功すると、セッション内にオブジェクトpassport: { user : obj }を作成する。この動作をシリアライズという。
    • シリアライズ時のコールバックに渡される第1引数は、認証成功時にdone()で返却したユーザ情報がそのまま渡る。
  • httpリクエストがあった際に、セッション内のオブジェクトがundefinedでなければ、req.userにオブジェクトを格納しルータに引き渡す。この動作をデシリアライズという。
  • オブジェクトの形式は任意でよく、StringでもJSONでもいい。

実装

  • シリアライズするオブジェクトは、{ username: String, password: String }としている。
  • ログアウトの動作として、セッション内のユーザ情報をundefinedにしている。
app.js
const express = require('express');
const session = require('express-session');

const app = express();

app.set('view engine', 'hbs');

app.use(express.urlencoded( { extended: false }));
app.use(session({
  secret: 'keyboard cat',
  resave: true,
  saveUninitialized: false,
}));
app.use('/', require('./routes/index'));


app.listen(3000, console.log('Server listening port 3000...'));
index.js
const express = require('express');
const router = express.Router();

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

const User1 = {
  name: "Taro",
  password: "Taro123"
};


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

    if(username !== User1.name){
      // Error
      return done(null, false);
    } else if(password !== User1.password) {
      // Error
      return done(null, false);
    } else {
      // Success and return user information.
      return done(null, { username: username, password: password});
    }
  }
));

passport.serializeUser((user, done) => {
  console.log('Serialize ...');
  done(null, user);
});

passport.deserializeUser((user, done) => {
  console.log('Deserialize ...');
  done(null, user);
});

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


router.get('/', (req, res) => {
  console.log(req.session);
  res.render('index', {user: req.user});
});

router.get('/failure', (req, res) => {
  console.log(req.session);
  res.send('Failure');
});

router.get('/success', (req, res) => {
  console.log(req.session);
  res.redirect('/');
});

router.post('/',
  passport.authenticate('local',
    {
      failureRedirect : '/failure',
      successRedirect : '/success'
    }
  )
);

router.post('/logout', (req, res) => {
  req.session.passport.user = undefined;
  res.redirect('/');
});

module.exports = router;
index.hbs
<!DOCTYPE html>
<body>
  {{#if user}}
    <h2>Hi {{user.username}}!! You are now logged in.</h2>
    <form action="/logout" method="post">
      <input type="submit" value="LOGOUT">
    </form>
    <hr>
  {{/if}}

  <form action="/" method="post">
    <input type="text" name="username" id="">
    <input type="password" name="password" id="">
    <input type="submit" value="Try Auth">
  </form>
</body>
</html>

その3. flashを利用する

動作概念

  • flashは一度表示、厳密には一度req.flash( '' )が実行されるまで、セッション内にメッセージを蓄積できる機能。
  • LocalStrategy内の認証失敗時のdone()の第3引数に { message : String } を渡すと、flashメッセージが蓄積され、req.flash('error')で呼び出せる。
    • メッセージのラベルは自動的にerrorになる点に注意。

実装

  • 再掲部分が多いので、差分にコメントを入れる。app.js は変更なし。
  • 認証失敗時のページでflashメッセージを表示するように変更した。
index.js
const express = require('express');
const router = express.Router();

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const flash = require('connect-flash'); //変更

const User1 = {
  name: "Taro",
  password: "Taro123"
};


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

    if(username !== User1.name){
      // Error
      return done(null, false, { message : 'User does not exist' }); //変更
    } else if(password !== User1.password) {
      // Error
      return done(null, false, { message : 'Password incorrect' }); //変更
    } else {
      // Success and return user information.
      return done(null, { username: username, password: password});
    }
  }
));

passport.serializeUser((user, done) => {
  done(null, user);
});

passport.deserializeUser((user, done) => {
  done(null, user);
});

router.use(passport.initialize());
router.use(passport.session());
router.use(flash()); //変更


router.get('/', (req, res) => {
  console.log(req.session);
  res.render('index', {user: req.user});
});

router.get('/failure', (req, res) => {
  console.log(req.session);
  res.render('index', { message: req.flash( 'error' )}); //変更
});

router.get('/success', (req, res) => {
  console.log(req.session);
  res.redirect('/');
});

router.post('/',
  passport.authenticate('local',
    {
      failureRedirect : '/failure',
      successRedirect : '/success',
      failureFlash: true   //変更
    }
  )
);

router.post('/logout', (req, res) => {
  req.session.passport.user = undefined;
  res.redirect('/');
});

module.exports = router;
index.hbs
<!DOCTYPE html>
<body>
  {{#if user}}
    <h2>Hi {{user.username}}!! You are now logged in.</h2>
    <form action="/logout" method="post">
      <input type="submit" value="LOGOUT">
    </form>
    <hr>
  {{/if}}
  {{#if message}}
    <p>{{message}}</p>
    <hr>
  {{/if}}

  <form action="/" method="post">
    <input type="text" name="username" id="">
    <input type="password" name="password" id="">
    <input type="submit" value="Try Auth">
  </form>
</body>
</html>

以上!

更新

  • ログアウト処理で、req.session.passport.user = undefined; としたが、req.logout();が標準で提供される

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
73