Node.js * Express * passport で認証する (TypeScript)

一回やっておくとfacebookとかtwitterとかgoogleとか応用が利く(500種類以上とかなんとか)





ログイン処理


まずpassportをインストール
npm install -S @types/passport


今回はローカル(固定のid, password)で認証する。
npm install -S passport-local


app.js (一部抜粋)
var express = require("express")
var session = require("express-session")
var passport = require('passport')

var app = express()

app.use(session({
  secret: "0.75feet pumpkin castle",
}))
app.use(passport.initialize())
app.use(passport.session())
var LocalStrategy = require('passport-local').Strategy

// view engine setup
app.set("views", path.join(__dirname, "views"))
app.set("view engine", "pug")

// 認証機構
passport.use(new LocalStrategy({
  usernameField: 'username',
  passwordField: 'password',
  passReqToCallback: true,
  session: false,
}, function (req, username, password, done) {
  process.nextTick(function () {
    if (username === "test" && password === "test") {
      return done(null, username)
    } else {
      console.log("login error")
      return done(null, false)
    }
  })

}))

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

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

app.use("/main", require("./routes/main"))
app.use("/login", require("./routes/login"))


deserializeUser
serializeUser
はセッションに登録したりセッションから取り出すときに利用されるらしい。

login.pug
doctype html
html
  head
    meta(http-equiv="X-UA-Compatible", content="IE=edge")
    meta(charset="UTF-8")
    meta( name="viewport", content="width=device-width, initial-scale=1")
    meta( name="robots" content="noindex, nofollow" )
    title= title
body
    form( method="post" action="/login" )
        input( type="text" name="username" )
        input( type="password" name="password" )
        button( type="submit") ログイン
    p= error


routes/login.ts
ログイン失敗時のエラーメッセージを雑に表示している。実際にはセッションなどを利用する。
import * as express from "express"
import * as passport from "passport"
{
  const router = express.Router()

  router.get("/", (req, res, { }) => {
    const title: string = "login"
    res.render("login", {
      title: title,
      // p= error のerrorが置き換えられる
      error: req.query.err ? "ユーザIDあるいはパスワードが正しくありません" : "",
    })
  })

  router.post("/", (req, res, { }) => {
    passport.authenticate("local", {
      // ログイン成功時のリダイレクト先
      successRedirect: "/manage",
      // ログイン失敗時のリダイレクト先
      failureRedirect: "/login?err=1",
    })(req, res)
  })

  module.exports = router
}

以上でusername,passwordに"test","test"を入力すると/mainに遷移できるようになる。
ただし直接アクセスされたら意味がないので、main.tsにアクセスされたときに認証済みかを確認する処理も必要になる。



各画面で認証済みか確認する


utils/util.ts
認証が必要な全画面で利用するための共通関数を用意する。
import * as express from "express"

export class Util {
  /**
   * 認証済みか確認
   * @example
   * router.get("/", isAuthenticated, (req, res, next) => {
   *   res.render("main")
   * })
   */
  public static isAuthenticated(req: express.Request, res: express.Response, next: express.NextFunction) {
    if (req.isAuthenticated()) {  // 認証済
      return next()
    }
    else {  // 認証されていない
      res.redirect("/login")  // ログイン画面に遷移
    }
  }

}

routes/main.ts
isAuthenticatedをrouter.getに噛ませるだけで認証処理が実装できる。
import * as express from "express"
import { Util } from "./../utils/util"
{
  const router = express.Router()

  // const isAuthenticated = (req: express.Request, res: express.Response, next: express.NextFunction) => {
  //   if (req.isAuthenticated()) {  // 認証済
  //     return next()
  //   }
  //   else {  // 認証されていない
  //     res.redirect("/login")  // ログイン画面に遷移
  //   }
  // }

  /* GET home page. */
  router.get("/", Util.isAuthenticated, ({ }, res, { }) => {
    res.render("main")
  })

  module.exports = router
}

認証済みでない場合ログイン画面へリダイレクト
それ以外は通常の処理が走る。

以下ハマりどころ

TypeError: req.flash is not a function

調べていないがv3からv4になったときになくなった?
ログイン失敗時のエラーメッセージを雑に表示している。実際にはセッションなどを利用する。
npm install -S connect-flash
req.flashを使わなければ不要。どちらかというと使わない方がいいか。

router.post("/", (req, res, { }) => {
passport.authenticate("local", {
successRedirect: "/main",
failureRedirect: "/login?err=1",
// Default はfalse
// failureFlash: false,
})(req, res)
})

connect-flashを使う場合セッション必須
npm i -S express-session

両方入力しないとreq.flashしたときエラーメッセージが Missing credentials になる
passport.authenticate の引数にいろいろ設定してみたりしたけどうまく動作しなかった。
ローカル認証だけの問題?

ページ毎に認証済みか確認しないといけない

当然ながらセッションを利用するようにしないと動かない。
またapp.use(passport.session())はapp.use(session())の後に書かないといけない。

app.use(session({
  secret: "0.75feet pumpkin castle",
}))
// 必ずセッション設定の後に記述すること
app.use(passport.initialize())
app.use(passport.session())



参考


http://www.passportjs.org/
https://github.com/jaredhanson/passport-local
https://github.com/passport/express-4.x-local-example/blob/master/server.js
https://qiita.com/kayanonaka/items/14804e3e233f1402ba12
https://qiita.com/tinymouse/items/fa910bf80a038c7f9ccb

2018年5月12日土曜日