質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.99%

NodeJS(Express4)+Passportでログイン認証後にセッションIDの再発行を行うには

受付中

回答 0

投稿

  • 評価
  • クリップ 1
  • VIEW 2,951

buibui80

score 971

Node.JS(Express4)+Passportのログイン認証後において、
ログイン処理時にセッションIDの再発行を行うにはどのようにすれば良いでしょうか?

app.post('/login', function(req, res, next) 内の
req.logIn にて req.session.regenerate を行いセッションIDの再発行を行うものの、
Cookieのconnect.sidが更新されずにセッションIDとの不一致の為にセッション切れを起こしてしまいます。
req.session.regenerate で行うものと思っていましたが違う手段があるのでしょうか?

※セッションIDの再発行箇所をコメントアウトしているので、
  セッション切れを確認するにはコメントアウトを切り替えてご確認ください。

どうぞよろしくお願い致します。


セッション管理用DBに Redis を使用しているので、Redisを起動した状態で確認をお願いします。
Redis含めた設定は app.js の appConfig に指定しています。
(当方ではRedis 3.0.2を使用しています)

※セッションの有効期限は appConfig.storeOption.ttl に秒指定しています。


[package.json]
{
  "name": "passport_test",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "body-parser": "~1.12.4",
    "connect-redis": "~2.3.0",
    "cookie-parser": "~1.3.5",
    "csurf": "~1.8.3",
    "debug": "~2.2.0",
    "ejs": "~2.3.1",
    "express": "~4.12.4",
    "express-generator": "~4.12.4",
    "express-session": "~1.11.3",
    "method-override": "~2.3.3",
    "morgan": "~1.5.3",
    "multer": "~0.1.8",
    "passport": "~0.2.2",
    "passport-local": "~1.0.0",
    "serve-favicon": "~2.2.1",
    "redis": "~0.12.1",
    "connect-flash": "~0.1.1",
    "bcrypt": "~0.8.3"
  },
  "devDependencies": {
    "nodemon": "~1.3.7"
  }
}
// app.js
var util            = require("util");
var logger          = require('morgan');
var favicon         = require('serve-favicon');
var path            = require('path');
var methodOverride  = require('method-override');
var cookieParser    = require('cookie-parser');
var bodyParser      = require('body-parser');
var multer          = require('multer');
var csurf           = require('csurf');
var bcrypt          = require('bcrypt');

var express         = require('express');
var session         = require('express-session');
var flash           = require("connect-flash");
var RedisStore      = require('connect-redis')(session);

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

var appConfig       = {
  pathInfo: {
    favicon    : path.join(__dirname, '/public/favicon.ico'),
    static: {
      views    : path.join(__dirname, 'views'),
      public   : path.join(__dirname, 'public'),
    }
  },
  storeOption: { // Redis Server
    host     : 'localhost',
    port     : 6379,
    prefix   : 'sess',
    ttl      : 30
  },
  sessionOption: {
    secret             : 'session_secret',
    resave             : true,
    saveUninitialized  : true ,
    store              : null,
    cookie             : {
      httpOnly  : true,
      secure    : false,
      path      : '/'
    }
  }
};

// ダミーデータ
var users = [
   { id: 1, username: 'b', password: 'bb', email: 'bbbb@example.com', provider: 'local' }
  ,{ id: 2, username: 'c', password: 'cc', email: 'cccc@example.com', provider: 'local' }
  ,{ id: 3, username: 'a', password: 'aa', email: 'aaaa@example.com', provider: 'local' }
];


//--------------
// custom utils


// ダミーデータアクセス用ユーティリティ
exports = module.exports = UserDataUtils = {
  
  // ユーザーIDを元にユーザー情報をコールバック
  findById: function(id, fn)
  {
    var idx = id - 1;
    if (users[idx])
    {
      fn(null, users[idx]);
    }
    else
    {
      fn(new Error('User ' + id + ' does not exist'));
    }
  },
  
  // ユーザー名を元にユーザー情報をコールバック
  // provider: local, twitter, etc.
  findByUsername: function(provider, obj, fn)
  {
    for (var i = 0, len = users.length; i < len; i++)
    {
      var user = users[i];
      switch(provider)
      {
        case "local":
          if (user.username === obj) { return fn(null, user); }
          break;
        case "twitter":
          if (user.profile_id === obj.profile.id) { return fn(null, user); }
          break;
        default:
      }
    }
    return fn(null, null);
  }
};


// Passport管理用ユーティリティ
exports = module.exports = PassportUtils = {
  
  ensureAuthenticated: function (req, res, next)
  {
    if (!req.isAuthenticated()) { return res.redirect('/'); }
    next();
  },
  
  serializeUser: function(user, done) {
    console.log("[passport] [serializeUser]", user);
    done(null, user.id);
  },
  
  deserializeUser: function(id, done) {
    console.log("[passport] [deserializeUser]", id);
    process.nextTick(function(){

      // data access
      UserDataUtils.findById(id, function (err, user) {
        done(err, user);
      });

    });
  },
  
  localStrategy: function(username, password, done) {
    process.nextTick(function(){
      UserDataUtils.findByUsername("local", username, function(err, user) {
        if (err)
        {
          console.log("[passport] [4]", username, password);
          return done(err);
        }
        if (!user)
        {
          console.log("[passport] [3]", username, password);
          return done(null, false, { message: username + 'は存在しないユーザーです' });
        }
        if (user.password != password)
        {
          console.log("[passport] [2]", username, password);
          return done(null, false, { message: 'パスワードに誤りがあります' });
        }
        console.log("[passport] [1]", username, password);
        return done(null, user);
      });
    });
  }
};



// app
var app              = express();
// set NODE_ENV=xxxxxxx; npm start;
var node_env         = app.get('env');

app.set('views', appConfig.pathInfo.static.views );
app.set('view engine', 'ejs');

//app.use(favicon(appConfig.pathInfo.favicon));
app.use(logger('dev'));
app.use(methodOverride());

app.use(cookieParser());
app.use(function(req, res, next){
  appConfig.sessionOption.store = new RedisStore(appConfig.storeOption);
  session(appConfig.sessionOption)(req, res, next);
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(multer());
app.use(flash());
//app.use(csurf());

app.use(express.static( appConfig.pathInfo.static.public ));


// passport
passport.serializeUser(PassportUtils.serializeUser);
passport.deserializeUser(PassportUtils.deserializeUser);
passport.use(new LocalStrategy({usernameField: 'username', passwordField: 'password'}, PassportUtils.localStrategy));
app.use(passport.initialize());
app.use(passport.session());


// routes
app.get('/logout', function(req, res){
  req.logout();
  req.flash('message', "ログアウトしました");
  res.redirect('/');
});

app.get('/', function(req, res){
  res.render('login', {
    user     : req.user,
    message  : req.flash('message')
  });
});

app.post('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    console.log("[post] [login]", err, user, info);
    if (err) { return next(err); }
    if (!user)
    {
      req.flash('message', info.message);
      return res.redirect('/');
    }
    req.logIn(user, function(err2) {
      if (err2) { return next(err2); }
      req.flash('message', "ログインしました");
      
      // ****************************************
      // セッションIDを再発行する場合
      //console.log("[regenerate] [before]", util.inspect(req.session.id));
      //console.log("[regenerate] [before]", util.inspect(req.cookies["connect.sid"]));
      //req.session.regenerate(function(err3) {
      //  console.log("[regenerate] [after]", util.inspect(req.session.id));
      //  console.log("[regenerate] [after]", util.inspect(req.cookies["connect.sid"]));
      //  
      //  return res.redirect('/account');
      //});
      
      // ****************************************
      // セッションIDを再発行しない場合
      return res.redirect('/account');
      
    });
  })(req, res, next);
});

app.get('/account', PassportUtils.ensureAuthenticated, function(req, res){
  res.render('account', {
    user     : req.user,
    message  : req.flash('message')
  });
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message : err.message,
    error   : err
  });
});

module.exports = app;
<!-- views/login.ejs -->
<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <h1>Login</h1>
  <h3><%= message %></h3>
  <!-- submit to /login -->
  <form action="/login" method="post">
    <input type="text" id="username" name="username" value="a">
    <input type="password" id="password" name="password" value="aa">
    <button type="submit">Submit</button>
  </form>
</body>
</html>
<!-- views/account.ejs -->
<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <h1>Account</h1>
  <h3><%= message %></h3>
  <a href="/logout">Logout</a> | 
  <a href="/account">account</a> | 
  <h3><%= user.id %></h3>
  <h3><%= user.username %></h3>
  <h3><%= user.password %></h3>
  <h3><%= user.email %></h3>
</body>
</html>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.99%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる