Express.jsとMySQLを使って認証機能を実装する

expressでは、passportの認証機能を利用するのが便利です。 twitterfacebookなどとも連携できます。 ここではローカルでの認証について、アウトプットとしてまとめました。

expressアプリの作成

# terminal

$ npm install -g express-generator
$ express --view=pug your-app-name
$ cd your-app-name
$ npm install

以降、

# terminal

$ npm start

でアプリケーションを起動させます。 http://localhost:3000

必要項目のインストール

# terminal

$ npm install --save passport
$ npm install --save passport-local
$ npm install --save express-session

ログイン実装

// app.js

// 既に色々入っているので、そこへ追加
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var session = require('express-session');

// signinページの追加
var signinRouter = require('.routes/signin');
app.use('/signin');

// session, passport.initialize, passport.sessionは以下の順番で追加
app.use(session({
  secret: "testing",
  resave: false,
  saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());

// authentication
passport.serializeUser(function(username, done) {
  console.log('serializeUser');
  done(null, username);
});

passport.deserializeUser(function(username, done) {
  console.log('deserializeUser');
  done(null, {name:username});
});

passport.use(new LocalStrategy(
  {
    // signinのformで定義したnameの要素をセット
    usernameField: "username",
    passwordField: "password"
  },
  function(username, password, done){
    // ここでは、データベースを使わずに、仮にusernameとpasswordを固定で入れています。
    if(username == "test" && password == 123456789){
      return done(null, username);
    }
    return done(null, false, {message: "invalid"});
  }
));

app.post('/signin',
  passport.authenticate('local',
    {
      failureRedirect: "/signin"
    }
  ),
  function(req, res, next){
    // res.redirect("/")でreq.userが渡せなかったので、ここでfetchを使っています。
    // https://github.com/jaredhanson/passport/issues/244
    // fetchは以下のようにインストール
    // npm install --save isomorphic-fetch
    // var fetch = require('isomorphic-fetch');
    fetch("http://localhost:3000/signin",
      {
        credentials: "include"
      }
    ).then(function(){
      res.redirect("/");
    }).catch(function(e){
      console.log(e);
    });
  }
);

で、index.pugへリダイレクトした時に、そのページ上にログインしたユーザーの名前を表示します。

// index.js

var express = require('express');
var router = express.Router();

// index.pugをgetした時にコールバックを実行
router.get('/', function(req, res, next) {
  console.log(req.user); // {name:'test'}
  if (req.user) { // ログインしているユーザーが存在する場合のみ有効
    res.render("index", { username: req.user.name});
  } else { // ユーザーが存在しなければ、サインインページへ飛ばされる
    res.redirect('/signin');
  }
});

module.exports = router;
// index.pug

extends layout

block content
  h1= username
  p Welcome to Express

こんな感じでtestの文字が出てきます。 Screen Shot 2018-07-18 at 23.15.22.png

signin.jsはindex.jsをコピペしてsignin用に書き換えるだけで良いです。

// signin.js

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.render("signin", {});
});

module.exports = router;

一応signin.pugも

// signin.pug

extends layout

block content
  form(action="/signin" method="post")
    .username
      label username
      input(type="text" name="username") // nameはnew LocalStrategyのところで使います。
    .password
      label password
      input(type="password" name="password") // nameはnew LocalStrategyのところで使います。
    .signin
      input(type="submit")

ログイン時にconsole.log('serializeUser')とconsole.log('deserializeUser')が実行されていることを確認しましょう。 serializeUserだけがコンソールで出力されていてもdeserializeUserが出力されていなければ、ログインは成功していません。 その時は、app.use時のsession、passport.initilizer、passport.sessionを書く順番がおかしくなっていないかとか、誤字脱字等がないかどうかとか、チェックしていってください。

MySQLのユーザー情報を基にログイン

mysqlで適当にデータベースを作ります。

# terminal

mysql> create table users (id int not null primary key, username varchar(20), password int);

とかにして、

# terminal

mysql> insert into users values (1, 'test', 123456789);

みたいにデータを入れておきます。 次にapp.jsでmysqlを使い、データを取り出してログイン時に入力された値と照合させるようにします。

// app.js

// npm install --save mysql2
var mysql = require('mysql2');
var connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'your-password', // mysqlの自分のパスワード
  database: 'testdb' // db名は自分で自由に作った名前を当てはめる
});

// 上で書き込んだpassport.useの部分を書き換えます。
passport.use(new LocalStrategy(
  {
    usernameField: "username",
    passwordField: "password"
  },
  function(username, password, done){
    connection.query("select * from users;", function(err, users) {
      // usernameもpasswordもユニーク前提
      var usernames = [];
      var passwords = [];
      for (i = 0; i < users.length; i++) {
        usernames.push(users[i].username);
        // input(type="password")で渡される値はstringのようなので、
        // データベースから取り出した値もstringにしています。
        var pw = users[i].password.toString();
        passwords.push(pw);
      }
      if (usernames.includes(username) && passwords.includes(password)) {
        return done(null, username);
      }
      return done(null, false, {message: "invalid"});
    });
  }
));

これで、ログイン時に ユーザー名: test パスワード: 123456789 を入れてあげると、ログインできるようになります。

サインアップは、サインアップのフォームを作って、post時に/signupへやってきた時のコールバックにロジックを書いてあげれば良いです。

// app.js

app.post('/signup', function(req, res, next){
  connection.query('select * from users;', function(err, users){
    connection.query('insert into users set ? ;', {
        username: req.body.username,
        email: req.body.email,
        password: req.body.password,
        created_at: new Date(),
        updated_at: new Date()
      },
      function(err){
        console.log("サインアップに関するエラー: " + err);
        res.redirect("/signin");
      }
    );
  });
});

まあ、こんな感じで。

サインアウトは、

// app.js

app.get('/signout', function(req, res, next) {
  req.logout();
  console.log('ログアウトしました');
  res.redirect('/');
});

これだけでOKです。 いい感じのところにサインアウトボタンを配置してあげてください。