trouble integrating express with passport - reactjs

I hope someone can help me out as ive spent far too long scratching my head over this issue. I have a small React, Express, MongoDb app which i want to integrate with Passport.js. However I just cannot get the passport to authenticate.
I have a controller set up for a route /login. I have a console log at the start of the file so I know that the code goes into that block. However the code doesnt enter the passport authenticate block.
const bcrypt = require('bcrypt');
const user = db.user;
const passport = require('passport');
exports.login = async (req, res, next) => {
console.log('inside login controller');
passport.authenticate('local', function (err, user, info) {
console.log('inside passport');
req.login(user, function (err) {
console.log('inside req login');
if (err) {
return res.status(400).json({ errors: err });
}
return res
.status(200)
.json({ success: `logged in ${user.id}` })
.redirect('/users/' + user);
})(req, res, next);
});
};
My server file looks like this (omitted a lot of stuff to save space but the gist is as follows):
const passport = require('./passport/setup');
const initialisePassport = require('./passport/setup');
const auth = require('./controller/user.controller');
app.use(
session({
secret: 'a_very_special_secret',
resave: false,
saveUninitialized: true,
expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
app.use(cookieParser('a_very_special_secret'));
app.use(initialisePassport.initialize());
app.use(passport.session());
The last file is the passport setup file which is here:
const bcrypt = require('bcrypt');
const db = require('../models');
const User = db.user;
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
//Local strategy
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
console.log('inside passport strat');
User.findOne({ email })
.then((user) => {
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: 'Wrong password' });
}
});
})
.catch((err) => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
Id love a helping hand on where im going wrong with this. So basically when i post to the route via the front end I get the message in my console that im inside the login controller but the authentication process doesnt happen.

Related

Passport.js authentication in ExpressJS not returning res.user in React

I've followed a guide on Medium on the implementation of Passport js with a MERN stack and whilst I got authentication to work, I'm struggling to persist users between routes. Below are snippets of my code:
Backend
Server.js setup (part of it):
const mongoose = require('mongoose')
const MongoStore = require('connect-mongo')
const passport = require('./passport/setup')
const MONGO_URI = 'db'
const app = express();
mongoose
.connect(MONGO_URI, { useNewUrlParser: true })
.then(console.log(`MongoDB connected ${MONGO_URI}`))
.catch(err => console.log(err))
app.set("")
const PORT = process.env.PORT || 5000;
if (PORT !== 5000) {
app.use(enforce.HTTPS({ trustProtoHeader: true }));
}
app.use(cors({
origin: true,
credentials: true,
}))
app.options('*', cors())
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
Passport.js Local Strategy setup:
const bcrypt = require('bcryptjs');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/User')
passport.serializeUser((user, done) => {
done(null, user.id);
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user);
});
});
passport.use(new LocalStrategy({ usernameField: "email" }, (email, password, done) => {
User.findOne({ email: email })
.then(user => {
if (!user) {
return done(null, false, { message: 'No User Found' })
} else {
bcrypt.compare(password, user.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: "Wrong Password" });
}
});
}
})
.catch(err => {
return done(null, false, { message: err });
});
})
);
module.exports = passport;
Login route:
router.post("/new-login", async (req, res, next) => {
passport.authenticate("local", function (err, user, info) {
if (err) {
return res.status(400).json({ errors: err });
}
if (!user) {
return res.status(400).json({ errors: "No user found" });
}
req.logIn(user, function (err) {
if (err) {
return res.status(400).json({ errors: err });
}
return res.status(200).json({ success: `logged in ${user.id}` });
});
})(req, res, next);
})
Frontend React:
Posting Login Data:
export const postData = async (url, data) => {
try {
console.log('posting this ', data)
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.post(url, data, config);
console.log('getting this', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
console.log('Error', exp)
return {
success: false,
error: exp.response.data,
};
}
};
Axios call:
let response = await postData('/login/new-login', { email, password })
When trying to login all of this works and returns the login success message with user.id from the login route, but when I look at the Axios response there's no reference to any user or session as shown below:
I'm probably not understanding how users are passed to React from the backend but shouldn't res.user exist here following req.LogIn in Passport? Or should I take the user.id and save it in a global React state and attach it every time I do a request to a protected route? Right now if I do a GET request to a protected route I get a req.user undefined message.
Frontend Axios call:
export const getData = async (url) => {
console.log('not called?')
try {
const config = {
withCredentials: true,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'backend',
'Access-Control-Allow-Methods': 'GET,PUT,POST,DELETE'
}
}
const response = await axios.get(url, config);
console.log('response? ', response)
return {
data: response.data,
error: "",
success: true,
};
} catch (exp) {
return {
success: false,
error: exp.response.data,
}
}
}
Backend Protected Route:
router.get("/new-protected", passport.authorize('local', { failureRedirect: '/login' }), (req, res) => {
console.log('whats request?', req.user)
res.send(`Hello, logged in`);
})
When trying this I remove the middleware to see what req looks like, but I always get undefined req.user, so I'm guessing either I'm supposed to be passing req.user somehow in my axios call or I'm not retrieving a user when logging in initially, any ideas on what I'm doing wrong?
Many thanks
You don't have to do anything about the user's data once you log in with authorization, the passport middleware is here for the job, however we should at least set up the right condition for them to exchange the credential, try to change the setting a bit to see how it go :
this part :
app.use(cors({
origin: //put the EXACT URL you host from Vercel
method: 'GET,PUT,POST,DELETE'
credentials: true,
}))
app.set('trust proxy', 1)
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: MONGO_URI })
})
);
app.use(express.json()); // optional: you can uninstall bodyParser since express have now his own module so you can just replace with this line.
app.use(passport.initialize());
app.use(passport.session());
and update a bit this part at the frontend Axios call :
const config = {
withCredentials: true,
};
I don't know if you have already the issue in localhost or after deployment, if it sill not working than we may have to fix on the cookie session.

Unable to verify user token stored in cookie in MERN app

I am trying to verify a logged In user token in my MERN. However, I am getting "undefined" on client-side as API response.
When I hit the endpoint http://127.0.0.1:1000/api/v1/users/login in postman to login a user and this endpoint http://127.0.0.1:1000/api/v1/users/verify to verify a user's token, the token is returned.
But when I do the same in my react app, the token returns undefined. I have spent hours debugging it but unable to figure it out.
Please I need help.
Here is my React (client side) code:
axios.defaults.withCredentials = true;
function Welcome() {
const [user, setUser] = useState("");
const sendRequest = async () => {
const res = await axios
.get("http://127.0.0.1:1000/api/v1/users/verify", {
withCredentials: true,
})
.catch((err) => console.log(err));
const data = await res.data;
return data;
};
React.useEffect(() => {
sendRequest().then((response) => console.log(response.data));
}, []);
return <div>Welcome</div>;
}
Here is my login endpoint.
exports.loginUser = async (req, res) => {
const { name, password } = req.body;
let existingUser;
try {
existingUser = await User.findOne({ name: name });
} catch (err) {
return new Error(err);
}
if (!existingUser) {
return res.status(400).json({ status: "fail", message: "user not found" });
}
const isPasswordCorrect = bcrypt.compareSync(password, existingUser.password);
if (!isPasswordCorrect) {
return res
.status(400)
.json({ status: "fail", message: "invalid email or password" });
}
const token = jwt.sign({ id: existingUser._id }, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
});
res.cookie("jwt", token, {
path: "/",
expires: new Date(Date.now() + 1000 * 70),
httpOnly: true,
sameSite: "lax",
});
return res
.status(200)
.json({ message: "Succesfully loggedIn", user: existingUser, token });
};
Here is my verifyToken endpoint
exports.verifyToken = (req, res, next) => {
const cookies = req.headers.cookie;
const token = cookies.split("=")[1];
if (!token) {
res.status(404).json({ message: "no token found" });
}
jwt.verify(String(token), process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(400).json({ message: "Invalid token" });
}
req.id = user.id;
});
next();
};
router.route("/verify").get(verifyToken, getUser);//endpoint
exports.getUser = async (req, res, next) => {
const userId = req.id;
let user;
try {
user = await User.findById(userId, "-password");
} catch (err) {
return new Error(err);
}
if (!user) {
return res.status(404).json({ status: "fail", message: "user not found" });
}
return res.status(200).json({ user });
};

What is the best way to use Google 0Auth

I'm trying to use Google OAuth in my App, on the Log In page and the Sign Up page, I'm looking for the best way and the easiest! I tried Passport Js, but I'm stuck right now.
I'm using Mongoose right now and I'm signing up and in users perfectly, but now i want to add a feature where the user can sign in using his google account, I'm looking for a way where the app can get the Email the user is using for his google account and then look if the email is already registered if so redirect him to the home page and if not sign his email up, save it to database, and then redirect to the home page.
This is how my Auth.js looks like
//REGISTER
router.post("/register", async (req, res) => {
try {
//generate new password
const salt = await bcrypt.genSalt(10);
const hashedPass = await bcrypt.hash(req.body.password, salt);
//create new user
const newUser = new User ({
username: req.body.username,
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: hashedPass,
repeatPassword: hashedPass,
birthday: req.body.birthday,
});
//save user and respond
const user = await newUser.save();
res.status(200).json(user);
} catch (err) {
res.status(500).json(err);
}
});
//LOGIN
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
// if(!user) return res.status(400).json("Wrong credentials!");
!user && res.status(400).json("Wrong credentials!");
const validated = await bcrypt.compare(req.body.password, user.password);
// if(!validated) return res.status(400).json("Wrong credentials!");
!validated && res.status(400).json("Wrong credentials!");
const { password, ...others } = user._doc;
return res.status(200).json(others);
} catch (err) {
return res.status(500).json(err);
}
});
PassportJs configuration I used: but didn't work
passport.use(
new GoogleStrategy(
{
clientID: "MyClientId",
clientSecret: "Myclientsecret",
callbackURL: "/api/auth/google/callback",
},
function (accessToken, refreshToken, profile, done) {
User.find(
{
social: profile.provider,
social_id: profile.id,
},
(err, user) => {
if (user.length === 0) {
const user = new User({
email: profile.email,
username: profile.displayName,
profilePic: profile.photos[0],
social: profile.provider,
social_id: profile.id,
});
const userModel = new User(data);
userModel.save();
done(null, profile);
}
if (err) {
return done(err);
}
},
);
return done(null, profile);
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
success: true,
message: "successfull",
user: req.user,
// cookies: req.cookies
});
}
});
router.get("/login/failed", (req, res) => {
res.status(401).json({
success: false,
message: "failure",
});
});
router.get("/google", passport.authenticate("google", { scope: ["profile"] }));
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: CLIENT_URL,
failureRedirect: "/login/failed",
})
);

How find some error send data react to backend?

I've seen in some videos that the username and password are sent via axios
this is:
const user_Login = (email, password) => async (dispatch) =>{
dispatch({ type: USER_LOGIN_REQUEST });
try {
const {data} = await axios( {
method: "POST",
data: {
username: email,
password: password
},
withCredentials: true,
url: "http://localhost:3001/login"
});
dispatch({ type: USER_LOGIN_SUCCESS, payload: data });
} catch (error) {
dispatch({type: USER_LOGIN_FAIL, error: error.message});
}
}
I used cors when I got the password with the username. but there is a problem I can't fix or find it. passport.serilizeUser change User id two times. and req.user cannot work.
This is backend:
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const bodyParser = require('body-parser');
const passport = require('passport');
const LocalStrategy = require('passport-local');
const passportLocalMongoose = require('passport-local-mongoose');
const session = require('express-session');
const app = express();
app.use(cors({
origin: "http://localhost:3000",
credentials: true
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended:true}));
app.use(session({
secret: "Our little secret.",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
mongoose.connect(
'mongodb://localhost:27017/testdb'
, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
});
const shema = new mongoose.Schema({
username: String,
password: String
});
shema.plugin(passportLocalMongoose);
const User = mongoose.model("users", shema);
// User.plugin( passportLocalMongoose );
passport.use(User.createStrategy());
passport.serializeUser(function(user, done) {
console.log("serilizeUser:", user.id);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log("deserlizeUser: ", id);
User.findById(id, function(err, user) {
done(err, user);
});
});
app.post('/register', (req, res) => {
console.log(req.body);
User.register( {username: req.body.username}, req.body.password, (err,user)=>{
if(err){
console.log(err);
}
else{
res.send("User successfully registered!");
}
});
});
app.get('/', (req, res) => {
res.send(req.user);
});
app.post('/login', function(req, res){
const _user = new User({
username: req.body.username,
password: req.body.password
});
req.login( _user, function(err){
if(err){
console.log(err);
}
else{
console.log("inside: ", req.user.id);
passport.authenticate("local")(req,res,function(){
console.log("ok!");
})
}
} );
res.send(req.user.id);
});
app.get('/', (req, res) => {
res.send(req.user);
});
app.listen(3001, () => {
console.log(`Server started on port 3001`);
});
when I send username and password I get console this:
serilizeUser: 607959e213f2692264c31c3b
inside: 607959e213f2692264c31c3b
serilizeUser: 60794a8736e1df1dfc16c37c
ok!
I think error with send username and password. Please help me!
I can't solve this problem for 5 days.
req.login() establishes a login session. passport.authenticate() middleware invokes req.login() automatically. passport.serializeUser will be invoked from req.login().
Here both of them are being used. Only passport.authenticate() should be fine to create a session and authenticate it. Check the follows,
app.post("/login", function (req, res) {
passport.authenticate("local")(req, res, function () {
console.log("ok!");
res.send(req.user.id);
});
});
I checked it in my local and works as you expected !!

PassportJS Express req.user not persisting in Heroku

I am using React and Express. Only the React is hosted on heroku and I am hosting the Express server locally. With Express I am using Passportjs with MongoDB.
The problem is that React clien t works well on local deployment, however once I deploy my React App on Heroku it does not work well propertly. When using my deployed React app, I can register a user but I cannot log in a user. It does not return error and POST("/login") returns "Successfully Authenticated" back to me and when I try to access req.user using axios it returns nothing back when it should be returning the user.
React Code
const getUser = () => {
axios({
method: "GET",
withCredentials: true,
url: "http://localhost:3001/user",
})
.then((res) => {
{
console.log(res.data);
if (res.data.username != null)
setMessage("Welcome " + res.data.username);
}
})
.catch((err) => console.log(err));
};
const loginTheUser = async () => {
await axios({
method: "POST",
data: {
username: username,
password: password,
},
withCredentials: true,
url: "http://localhost:3001/login",
}).then((res) => {
if (res.data === "Successfully Authenticated") {
history.push("/");
}
console.log(res.data);
});
await getUser();
};
Express code
// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(
cors({
origin: [
"http://localhost:3000",
"https://my-herokuapp.com",
], // <-- location of the react app were connecting to
credentials: true,
})
);
app.use(
session({
secret: "secretcode",
resave: true,
saveUninitialized: true,
})
);
app.use(cookieParser("secretcode"));
app.use(passport.initialize());
app.use(passport.session());
require("./passportConfig")(passport);
//Login
app.post("/login", (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) throw err;
if (!user) {
console.log("Unsuccessful login");
res.send("Unsuccessful login");
} else {
req.logIn(user, (err) => {
if (err) throw err;
{
console.log("Login success");
res.send("Successfully Authenticated");
}
});
}
})(req, res, next);
});
//Register
app.post("/register", (req, res) => {
User.findOne({ username: req.body.username }, async (err, doc) => {
if (err) throw err;
if (doc) res.send("User Already Exists");
if (!doc) {
const hashedPassword = await bcrypt.hash(req.body.password, 10);
const newUser = new User({
username: req.body.username,
password: hashedPassword,
});
await newUser.save();
res.send("User Created");
}
});
});
//get User
app.get("/user", (req, res) => {
res.send(req.user); // The req.user stores the entire user that has been authenticated inside of it.
});
Passport config
const User = require("./user");
const bcrypt = require("bcryptjs");
const localStrategy = require("passport-local").Strategy;
module.exports = function (passport) {
passport.use(
new localStrategy((username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) throw err;
if (!user) return done(null, false);
bcrypt.compare(password, user.password, (err, result) => {
if (err) throw err;
if (result === true) {
return done(null, user);
} else {
return done(null, false);
}
});
});
})
);
passport.serializeUser((user, cb) => {
cb(null, user.id);
});
passport.deserializeUser((id, cb) => {
User.findOne({ _id: id }, (err, user) => {
const userInformation = {
username: user.username,
};
cb(err, userInformation);
});
});
};

Resources