So, I have a simple Node Express server and I am handling authentication with Passport.js. I am struggling when trying to make it work correctly.
Here's the setup:
I have a React frontend where users can input their email and password in a form. (Shouldn't be the problem, as I tried to make the same POST request from an html and script file, got the same error).
Upon form submission, the application should send a POST request to the server (http://localhost:8080/login) with the email and password data.
The frontend application is using Axios to send the POST request to the backend. (Tried fetch, same response).
The server should:
Receive email and password.
Verify these credentials are OK.
Serialize/deserialize user.
Redirect to "/auth/jwt" which is protected by passportAuth middleware. ("/auth/jwt" is not implemented yet, so this route just gets de req and sends a 200 status, it does nothing else).
And here remains the problem. In the passportAuth middleware, which is essentialy, "req.isAuthenticated()", returning false UNLESS I send the email and password from postman, which makes everything very frustrating.
Sessions are getting stored in MongoDB and the key "passport" does exist with user's _id, as expected, but I do not know why isn't it working.
My thoughts:
Shoudn't be cors problem, as I set app.use(cors()).
Client request should be fine.
Web app and postman's request are the same, just email and password.
Headers problem? Tried different configs, still not working.
React Frontend:
...
const userData = { email, password };
const config = { headers: { "content-type": "application/json" }};
const res = await axios.post("http://localhost:8080/login", userData, config);
console.log(res);
...
Server.js
const initializePassport = require("./passport/local");
const sessionConfig = {
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false, // Resave and saveUninitialized: tried false and true.
cookie: {
secure: false,
httpOnly: false,
},
store: new mongoStore({
mongoUrl: URI_CLOUD_CONNECTION,
ttl: 60 * 30,
expires: 60 * 30,
autoRemove: "native",
}),
}
try {
await mongoose.connect(URI_CLOUD_CONNECTION);
initializePassport(passport); // local.js
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(SESSION_SECRET));
app.use(cors());
app.use(expressSession(sessionConfig)); // express-session
// Passport initialization
app.use(passport.initialize());
app.use(passport.session());
Login route:
// "/login"
router.route("/").post(PassportController.login);
PassportController:
const passport = require("passport");
const login = passport.authenticate("login", {
successRedirect: "/auth/jwt",
})
"/auth/jwt" route:
const passportAuth = require("../../middlewares/passportAuth");
const JwtController = require("../../controllers/authControllers/jwt-controller");
// "/auth/jwt"
router.get("/", passportAuth, JwtController.generateTokenAndRedirect); // This JwtController just sends status 200.
PassportAuth middleware:
module.exports = (req, res, next) => {
if (!req.isAuthenticated()) {
logger.error("User not authenticated by passport!");
return res.sendStatus(401);
}
console.log("Passport ID en session: ", req.session.passport);
logger.info("PassportAuth authenticated.");
next();
};
Local.js (passport main logic):
const LocalStrategy = require("passport-local").Strategy;
module.exports = (passport) => {
const authenticateUser = async (req, email, password, done) => {
try {
const user = await userModel.getUserByEmail(email);
if (!user) {
const errorMessage = { message: "Requested user does not exist." };
return done(errorMessage, false);
}
const isPasswordValid = await userModel.isPasswordValid(email, password);
if (!isPasswordValid) {
const errorMessage = { message: "Wrong password." };
return done(errorMessage, false);
}
done(null, user); // This works just fine.
} catch (error) {
logger.error("Error in local.js.", error?.message);
return done(error);
}
};
passport.use("login", new LocalStrategy({
usernameField: "email",
passwordField: "password",
passReqToCallback: true,
}, authenticateUser));
passport.serializeUser((user, done) => {
console.log("Serializing user (only _id)...", user._id.toString());
return done(null, user._id.toString());
});
passport.deserializeUser(async (_id, done) => {
console.log("Deserialize userId: ", _id);
const user = await userModel.findById(_id);
const userData = {
_id: user._id.toString(),
email: user.email,
firstname: user.firstname,
lastname: user.lastname,
}
done(null, userData);
});
};
It is in passportAuth "isAuthenticated()" where I have this issue with postman and web app. When using postman, it does work and execute next() function. But when requesting from client, it doesn't.
I am losing my mind as I do not understand what is the issue.
Steps I console.logged:
When hiting login:
Email and password are valid.
Serializing user... (_id)
Error User is not authenticated by passport!
When trying with postman...
Email and password are valid.
Serializing user... (_id)
Deserializing user... (same _id)
Passport ID in session = { user: "(_id number)" }
Passport authenticated
Sending status 200 OK.