'Passport.js isAuthenticated() always returns false when on heroku, but works properly when server is run locally
I am using passport.js for authentication as well as express-sessions to store cookies on the client. Additionally, my client is a react app. I still am not sure if this is a server-side issue or client-side issue. Anyways, when I run my node server locally, then log in from my react app, the cookie is stored locally and I am authenticated, so if I access a route that requires authentication, I get access. However, when I run the same exact server code on Heroku, and the same react app, and I log in from the React app, passport. authenticate works and send a success message back to the client however for some reason now if I access a protected route my server cannot authenticate the client. I know that the cookie is stored because as soon as I switch to my local server, I become authenticated again, which means the cookie is with the client. I'm not sure what the issue is.
Node.js server code BACKEND
const app = express();
mongoose.connect("mongodb+srv://admin:" + process.env.DBPASS + "@cluster0.xpbd4.mongodb.net/" + process.env.DBNAME + "?retryWrites=true&w=majority", {
useNewUrlParser: true,
useUnifiedTopology: true
});
mongoose.set("useCreateIndex", true);
app.use(cors({origin: "http://localhost:3000", credentials: true}));
app.use(bodyParser.urlencoded({
extended: true
}));
app.enable('trust proxy'); // add this line
app.use(session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(User.createStrategy());
passport.serializeUser(function(user, done) {
console.log("serializing user")
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializeUser called: " + id)
User.findById(id, function(err, user) {
done(err, user, function(){
console.log("deserializeUserError: " + err)
});
});
});
Login route
app.post("/login", function(req, res) {
const user = new User({
email: req.body.email,
password: req.body.password
});
req.login(user, function(err) {
console.log("req.login called")
if (err) {
console.log(err)
res.send("Nuts! An unknown error occured")
} else {
passport.authenticate("local")(req, res, function() {
res.send("success")
});
}
});
});
the route that tells the user if they're authenticated or not. Returns false when the server is on Heroku, but true when run locally.
app.get("/user/auth", function(req, res){
console.log("auth called");
if (req.isAuthenticated()) {
res.send("true")
} else {
res.send("false")
}
})
FRONT END React App where I log in the user
function submit(event){
event.preventDefault();
axios({
method: 'post',
data: Querystring.stringify(user),
withCredentials: true,
url: props.serverURL + '/login',
headers: {
'Content-type': 'application/x-www-form-urlencoded'
}
}).then(res => {
console.log("resdata: " + res.data)
if (res.data === "success"){
props.reAuth();
props.history.push('/');
}else{
setResult((prevValue) => {
return (
res.data.toString()
)
});
}
}).catch(function(err){
setResult((prevValue) => {
return (
"Invalid email or password"
)
});
})
}
The part of the recent app that checks for authentication
function reAuth(){
axios.get(serverURL + "/user/auth", {withCredentials: true}).then(res => {
console.log(res.data)
setAuth(res.data)
})
}
Solution 1:[1]
I had a problem where my passport session did not survive res.redirect and I found out that I was missing a return statement in the serialize and deserialize functions even though I was running locally it might do the trick for you
passport.serializeUser(function(user, done) {
console.log("serializing user")
return done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializeUser called: " + id)
User.findById(id, function(err, user) {
return done(err, user, function(){
console.log("deserializeUserError: " + err)
});
});
});
Solution 2:[2]
I encountered a similar issue when deploying a MERN app to Heroku and found this answer on SO helpful: https://stackoverflow.com/a/66395281/16921734.
In essence, you need to add the following to your session configuration:
- proxy : true
- sameSite : 'none'
Setting sameSite to none allows the browser to set cookies with cross domains, which is disabled by default.
With this, my session configuration looks like:
const sessionConfig={
store:MongoDBStore.create({ mongoUrl:dbUrl}),
secret:'thisisnotagoodsecret',
resave: false,
saveUninitialized: true,
cookie: {
httpOnly:true,
secure:true,
maxAge: 1000*60*60*24*7,
sameSite:'none',
},
proxy: true,
};
app.use(session(sessionConfig));
Hope this helps!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Ron Vaknin |
Solution 2 |