passport.js 中的 serializeUser 不会将用户数据添加到 express.session 中的会话

问题描述 投票:0回答:0

我正在构建一个 MERN 堆栈应用程序,在使用 render.com 部署我的站点时,在生产过程中身份验证和 passport.js 出现问题。

现在,身份验证、passport.js 和一切都在本地主机上工作,但在部署时不起作用。这可能意味着存在会话/cookie 问题。

我的应用程序有两种方法:本地主机和谷歌身份验证。两者都遇到了同样的问题:serializeUser 没有将用户数据放入会话中。我之所以知道这一点,是因为我在代码中添加了 console.log 语句。这是我进行谷歌身份验证时发生的情况的示例:

Apr 5 10:44:08 PM  [0] Session Details: Session {
Apr 5 10:44:08 PM  [0]   cookie: {
Apr 5 10:44:08 PM  [0]     path: '/',
Apr 5 10:44:08 PM  [0]     _expires: 2023-04-09T02:44:08.190Z,
Apr 5 10:44:08 PM  [0]     originalMaxAge: 259200000,
Apr 5 10:44:08 PM  [0]     httpOnly: true,
Apr 5 10:44:08 PM  [0]     secure: true,
Apr 5 10:44:08 PM  [0]     sameSite: 'strict'
Apr 5 10:44:08 PM  [0]   }
Apr 5 10:44:08 PM  [0] }
Apr 5 10:44:08 PM  [0] <MESSAGE THAT CONTAINS DEVICE/BROWSER INFO THAT SENT REQUEST, I REDACTED THIS>
Apr 5 10:44:11 PM  [0] Session Details: Session {
Apr 5 10:44:11 PM  [0]   cookie: {
Apr 5 10:44:11 PM  [0]     path: '/',
Apr 5 10:44:11 PM  [0]     _expires: 2023-04-09T02:44:11.169Z,
Apr 5 10:44:11 PM  [0]     originalMaxAge: 259200000,
Apr 5 10:44:11 PM  [0]     httpOnly: true,
Apr 5 10:44:11 PM  [0]     secure: true,
Apr 5 10:44:11 PM  [0]     sameSite: 'strict'
Apr 5 10:44:11 PM  [0]   }
Apr 5 10:44:11 PM  [0] }
Apr 5 10:44:11 PM  [0] Attempting google callback...
Apr 5 10:44:11 PM  [0] Google callback succesful.
Apr 5 10:44:11 PM  [0] serialize user is working
Apr 5 10:44:11 PM  [0] user object is: {
Apr 5 10:44:11 PM  [0]   _id: new ObjectId("<long string of id, redacted>"),
Apr 5 10:44:11 PM  [0]   googleId: '<long string of id, redacted>',
Apr 5 10:44:11 PM  [0]   email: '[email protected]',
Apr 5 10:44:11 PM  [0]   __v: 0
Apr 5 10:44:11 PM  [0] }
Apr 5 10:44:11 PM  [0] user_id object is: <long string of id, redacted>

serializeUser 有效,它确实从 google 身份验证中获取了正确的信息(如上面的“用户对象是”console.log 所示 下面是我的 passport.js 代码:

const passport = require("passport");
const bcrypt = require("bcryptjs");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const LocalStrategy = require("passport-local").Strategy;
const User = require('./models/User');

// google authentication passport 
passport.use(
  "google",
  new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    callbackURL: `${process.env.SERVER_URL}/auth/google/callback`,
    passReqToCallback: true
  },
  async function(req, accessToken, refreshToken, profile, done) {
    console.log("Attempting google callback...");
    try {
          let user = await User.findOne({ googleId: profile.id });
        if (!user) {
            // Create a new user if not found
            user = new User({
                googleId: profile.id,
                email: profile.emails[0].value,
                refreshToken: refreshToken // Store refresh token in the user model
            });
        } else {
            user.refreshToken = refreshToken; // Update refresh token
        }
        await user.save();
    console.log("Google callback succesful.");
        return done(null, user);
    } catch (err) {
      console.log("Google callback unsuccesful.");
          console.error('Error in GoogleStrategy callback:', err); // Log the error for debugging
      return done(err);
    }
  }
));

// local signup passport
passport.use (
  "local_signup",
  new LocalStrategy ({
    usernameField: "username",
    passwordField: "password",
    passReqToCallback: true,
  },
  async (req, username, password, done) => {
  try {
    console.log("local signup function is being called...");
    // Check if email is in a valid format
    const emailRegex = /^[\w-]+(.[\w-]+)*@([\w-]+.)+[a-zA-Z]{2,7}$/;
    if (!emailRegex.test(req.body.email)) {
      return done(null, false, {
        message: "Please provide an email with valid format.",
      });
    }

    // Check if password meets the requirements
    if (password.length < 6) {
      return done(null, false, {
        message: "Password must be at least 6 characters long.",
      });
    }
    const existingUser = await User.findOne({
      username: username
    });
    const existingEmail = await User.findOne({
       email: req.body.email
    });
    if (existingUser) {
      return done(null, false, {
        message: "Username already exists.",
      });
    }
    if (existingEmail) {
      return done(null, false, {
        message: "Email already exists.",
      });
    }
    const hashedPassword = await bcrypt.hash(password, 10);
    console.log("Got past the checkers");
    const newUser = new User({
      email: req.body.email,
      username: username,
      password: hashedPassword,
    });
    console.log("Made new schema for user");
    await newUser.save();
    console.log("Saved it!!!");
    return done(null, newUser);
    } catch (err) {
      console.log("Apparently something went wrong somewhere...");
      console.error("Something went wrong, and we don't even know why.", err);
      return done(err);
    }
  })
);  

// local login passport
passport.use(
    "local_login",
    new LocalStrategy({ usernameField: "identifier" }, async (identifier, password, done) => {
    try {
      console.log("Local LOGIN WORKING");
      const user = await User.findOne(
        { username: identifier }
      );
      const email = await User.findOne(
        { email: identifier }
      );
      if (!(user || email)) {
        if(!user && email == null) {
          return done(null, false, { message: "There are no accounts with the given username." });
        }
        if(!email && user == null) {
          return done(null, false, { message: "There are no accounts with the given email." });
        }
      }
      const isMatch = await bcrypt.compare(password, user.password);
      if (!isMatch) {
        return done(null, false, { message: "Incorrect password." });
      }
      // succesful
      return done(null, user);
    } catch (err) {
        return done(err);
    }
  })
);

passport.serializeUser((user, done) => {
  console.log("serialize user is working");
  console.log("user object is: " + user);
  console.log("user_id object is: " + user._id);
  done(null, user.id);
});

passport.deserializeUser(async (userId, done) => {
  console.log("attempting deserialize");
  try {
    const user = await User.findById(userId);
    if (user) {
      console.log("deserialize working.. I think");
      done(null, user);
    } else {
      console.log("deserialize not working");
      done(new Error('User not found'));
    }
  } catch (err) {
    done(err);
  }
});

下面是我的 server.js 代码:

const express = require("express");

// require dotenv
require('dotenv').config({ path: './.env' });

// require middlewares
const compression = require("compression");
const helmet = require("helmet");
const RateLimit = require("express-rate-limit");
const logger = require("morgan");
const cors = require("cors");
const session = require("express-session"); 
const MongoStore = require("connect-mongo");
var cookieParser = require('cookie-parser')
const passport = require("passport");

// require configuration stuff
const InitiateMongoServer = require("./db");
const passportStrategy = require("./passport");

// require routes
const auth = require("./routes/auth");
const user = require("./routes/user");

/* setup stuff */
InitiateMongoServer();
const app = express();
const PORT = process.env.PORT || 4000;

// the server will handle at maximum 60 requests per minut
const limiter = RateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 60,
});

app.use(cookieParser());
app.use(session({
  secret: 'imnotgivingawayprivateinformationinstackoverflow', 
  resave: false,
  saveUninitialized: true,
  store: MongoStore.create({ mongoUrl: process.env.MONGODB_CONNECT_URL }),
  cookie: {
    secure: true, // set to true for production https
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 72 * 60 * 60 * 1000 // 3 days
  }
}));


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

const printSessionDetails = (req, res, next) => {
  console.log('Session Details:', req.session);
  next();
};

// app.use(refreshSession);
app.use(printSessionDetails);

// middleware stuff
app.use(logger('combined'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(helmet());
app.use(compression());

// updated cors to allow cookies to be set in cross domain
app.use(cors(function (req, cb) {
  let corsOptions;
  corsOptions = {
    origin: process.env.CLIENT_URL,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'],
    credentials: true
  };
  cb(null, corsOptions);
}), function (req, res, next) {
  res.header('Access-Control-Allow-Credentials', true);
  next();
});


// Middleware for login
app.use("/auth", auth);
app.use("/user", user);

app.listen(PORT, (req, res) => {
  console.log(`Server starting at PORT ${PORT}`);
});

module.exports = app;

下面是我用于处理身份验证内容的控制器代码(路由在另一个文件中)

const passport = require("passport");

exports.success = (req, res) => {
    if (req.user) {
        res.redirect(process.env.CLIENT_URL + "/checklist");
    } else {
        res.redirect(process.env.CLIENT_URL + "/login");
    }
};

exports.fail = (req, res) => {
    res.redirect(process.env.CLIENT_URL + "/login");
};

exports.success2 = (req, res) => {
    if (req.user) {
        res.json({ success: true });
    } else {
        res.status(401).json({ success: false, message: "Something went wrong. Please try again."});
    }
};

exports.fail2 = (req, res, errorMessage) => {
    res.status(401).json({ success: false, message: errorMessage});
};


// (step 1) authenticates with google page
exports.google = passport.authenticate(
    "google", 
    { scope: [ 'email', 'profile' ]}
);
// (step 2) log in or create account
exports.google_callback = (req, res, next) => {
    passport.authenticate("google", (err, user, info) => {
        if (err) {
            return exports.fail(req, res, next);
        }
        if (!user) {
            return exports.fail(req, res, next);
        }
        req.logIn(user, (err) => {
            if (err) {
                return exports.fail(req, res, next);
            }
            return exports.success(req, res, next);
        });
    })(req, res, next);
};

exports.signup = (req, res, next) => {
    console.log("controller for signup calling");
    passport.authenticate("local_signup", (err, user, info) => {
        console.log('Passport authenticate callback');
        if (err) {
            console.error('Error:', err);
            return res.status(500).json({ success: false, message: "Something went wrong. Please try again." });
        }
        if (!user) {
            console.log('No user:', info.message);
            return res.status(401).json({ success: false, message: info.message });
        }
        req.logIn(user, (err) => {
            if (err) {
                console.error('Error logging in:', err);
                return res.status(500).json({ success: false, message: "Something went wrong. Please try again." });
            }
            console.log('User logged in');
            return res.json({ success: true });
        });
    })(req, res, next);

};
  
exports.login = (req, res, next) => {
    passport.authenticate("local_login", (err, user, info) => {
        if (err) {
            return exports.fail2(req, res, info.message, next);
        }
        if (!user) {
            return exports.fail2(req, res, info.message, next);
        }
        req.logIn(user, (err) => {
            if (err) {
                return exports.fail2(req, res, info.message, next);
            }
            return exports.success2(req, res, next);
        });
    })(req, res, next);
};

exports.logout = (req, res) => {
    console.log('attempting to log out of session');
    try {
        req.session = null;
        res.json({ loggedOut: true });
    } catch (err) {
        res.json({ loggedOut: false });
    }
};

exports.authenticationCheck = (req, res, next) => {
    console.log("req.user is: " + req.user);
    console.log("req.session is: " + req.session);
    console.log("Checking authentication status: ");
    if (req.isAuthenticated()) {
        console.log("Authentication Check is working");
        res.json({ isAuthenticated: true });
    } else {
        console.log("Authentication Check is not working");
        res.json({ isAuthenticated: false });
    }
}
  1. 我试过在 cookie-session 和 express-session 之间进行试验。我相信在当天早些时候,使用 cookie-session 以某种方式使它起作用(护照信息存储在会话中),但后来我相信相同的代码停止工作。老实说,我不知道该选择哪个,但是 cookie-session 后来给了我一个空会话,所以我决定使用 express-session。

  2. 我尝试检查我的环境变量、URL 连接或谷歌控制台设置是否有问题以及是否有正确的域/URL。他们都是。

  3. 我使用 chatGPT 的建议多次更改了 serializeuser 和 deserializeUser。我什至不知道我是否可以列出所有这些,但我的 passport.js 和 server.js 代码一团糟。

  4. 我的代码适用于我设备上的本地主机。当我部署渲染(前端的静态服务和后端的 Web 服务)时,serializeUser 无法存储到会话中。

我也不完全确定这里的会话或 cookie 有何不同。

node.js express passport.js express-session cookie-session
© www.soinside.com 2019 - 2024. All rights reserved.