我正在构建一个 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 });
}
}
我试过在 cookie-session 和 express-session 之间进行试验。我相信在当天早些时候,使用 cookie-session 以某种方式使它起作用(护照信息存储在会话中),但后来我相信相同的代码停止工作。老实说,我不知道该选择哪个,但是 cookie-session 后来给了我一个空会话,所以我决定使用 express-session。
我尝试检查我的环境变量、URL 连接或谷歌控制台设置是否有问题以及是否有正确的域/URL。他们都是。
我使用 chatGPT 的建议多次更改了 serializeuser 和 deserializeUser。我什至不知道我是否可以列出所有这些,但我的 passport.js 和 server.js 代码一团糟。
我的代码适用于我设备上的本地主机。当我部署渲染(前端的静态服务和后端的 Web 服务)时,serializeUser 无法存储到会话中。
我也不完全确定这里的会话或 cookie 有何不同。