3
1

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 3 years have passed since last update.

Passportを使って認証機能を実装する

Posted at

概要

Passportpassport-localを使ってごく簡単な認証フローを確認してみます。

Passportの公式サイト

ソースコード

今回の実装内容のソースコードは以下にあります。

環境

ソフトウェア バージョン
Node.js 14.15.0
npx 6.14.8
Express 4.16.1
Passport 0.4.1
passport-local 1.0.0

Expressアプリを作成する

express-generatorを使って今回認証機能を実装するためのExpressアプリを作ります。

npx express-generator --view=pug express-passport
cd express-passport
yarn install
yarn start

ログインページを作成する

/loginに対してPOSTリクエストできるページを作成します。

views/login.pug
extends layout

block content
  h1 ログイン

  form(action="/login" method="POST")
    div
      label(for="username") ユーザ名
      input(type="text" name="username")
    div
      label(for="password") パスワード
      input(type="password" name="password")
    div
      button(type="submit") ログイン

Passportを使って認証APIを作成する

最初に必要なライブラリをインストールします。

yarn add passport passport-local express-session

Passportの基本

Passportでは、認証を行うのに以下の3つを設定する必要があります。
(セッションは任意ですが、リクエストの度に認証するAPIでもなければ基本的に必要となります)

  • ストラテジー(Authentication strategies)
  • ミドルウェア(Application Middleware)
  • セッション(Sessions)

ストラテジーを設定する

Passportでは、ストラテジーという方法でリクエストを認証します。

ストラテジーは今回実装するユーザ名・パスワードの組み合わせの検証(passport-local)のほか、OAuthを使った委任認証、OpenIDを使った連携認証などいろいろあります。

また、ストラテジーはリクエストの認証に使われるよりも前に設定しておく必要があります。
今回はapp.jsに以下のような感じで実装しました。

app.js
const passport = require('passport');
const LocalStorategy = require('passport-local').Strategy;

const users = [
  { username: 'alice', password: 'alice', age: 22 },
  { username: 'bob', password: 'bob', age: 21 },
  { username: 'carol', password: 'carol', age: 30 }
]

const User = {
  findOne({ username }) {
    return users.find(user => user.username === username) || null
  }
}

passport.use(new LocalStorategy(function(username, password, done) {
  try {
    const user = User.findOne({ username })
    if (user == null) {
      return done(null, false)
    }
    if (user.password !== password) {
      return done(null, false)
    }
    delete user.password
    return done(null, user)
  } catch (err) {
    done(err)
  }
}))

LocalStorategyの引数として渡された関数はverify callbackと呼ばれ、ここでは与えられた認証情報(credentials)を有するユーザを検索して認証情報が妥当かどうか検証しています。
認証の結果に応じてdone()関数に対して以下のように引数を渡しています。

認証結果 第1引数 第2引数
成功 null 認証されたユーザオブジェクト
失敗 null false
サーバエラー Errorオブジェクト -

サーバ側でエラーが発生した(例えばDBに接続できないなど)場合、上記のように第1引数にエラーを入れておきます。クライアント側のエラーであれば基本的にエラーは入れず、認証失敗としてdone(null, false)とします。

passport.initialize()ミドルウェアで初期化する

永続的なセッションが必要ならexpress-sessionミドルウェア及びpassport.session()を使う必要があります。

app.js
app.use(session({
  secret: "cats",
  resave: false,
  saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());

ちなみに公式サイトでは、以下のようにbody-parserをインストールしていますが、

app.use(bodyParser.urlencoded({ extended: false }));

v4.16.0以降のExpress.jsでは標準搭載されているそうです。

参考: Body-ParserがExpressにexpress.json()として標準搭載されている話 - Qiita

今回はexpress-generatorで作成しているので既に必要な記述はされています。

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

ユーザのシリアライズ・デシリアライズ

永続的なセッションが必要な場合、セッションに対してユーザインスタンスをシリアライズして保存し、リクエストが来たときにセッションからユーザインスタンスをデシリアライズして保存できるようにします。

app.js
passport.serializeUser(function(user, done) {
  done(null, user.username);
});

passport.deserializeUser(function(username, done) {
  const user = User.findOne({ username })
  done(null, user);
});

なるべく保存されるセッション内のデータ量を小さくする為にここではuser.usernameだけ入れています。

認証APIを作成する

routes/index.js
router.post('/login', passport.authenticate('local', {
  successRedirect: '/',
  failureRedirect: '/login',
}))

router.post('/logout', function(req, res, next) {
  req.logOut();
  res.redirect('/login');
})

認証チェックミドルウェアを作成する

認証チェックミドルウェアを作って、アクセスを制限したいパスに対して付与します。

middleware/authenticated.js
module.exports = function (req, res, next) {
  if (!req.isAuthenticated()) {
    res.redirect('/login')
    return
  }
  next()
}

こんな感じのミドルウェアを作って、例えば/に付与すると、認証されていなければアクセスすることができないようになります。

routes/index.js
const authenticated = require('../middleware/authenticated');

router.get('/', authenticated, function(req, res, next) {
  res.render('index', { title: 'Express', username: req.user.username });
});

動作確認

yarn start

http://localhost:3000 にアクセスし、認証されていないので/loginに遷移することを確認。

適当なユーザでログインしてクッキーがセットされることを確認します。

HTTP/1.1 302 Found
Set-Cookie: connect.sid=*****; Path=/; HttpOnly

補足

password-localではユーザ名とパスワードのプロパティ名がデフォルトでusernamepasswordですが、変更する場合はストラテジーを設定する際に以下のように設定を追加します。

passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'passwd'
  },
  function(username, password, done) {
    // ...
  }
));

参考

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?