I'm trying to use koa-passport for koa2, and followed the examples of the author, but i always get "Unauthorized". I used the console.log and found that it even not hit the serializeUser.
var UserLogin = async (ctx, next) =>{
return passport.authenticate('local', function(err, user, info, status) {
if (user === false) {
ctx.body = { success: false }
} else {
ctx.body = { success: true }
return ctx.login(user)
}
})(ctx, next);
};
And then I searched on the web and found another writing of router, it goes to the serializeUser but the done(null, user.id) threw error that "cannot get id from undefined".
let middleware = passport.authenticate('local', async(user, info) => {
if (user === false) {
ctx.status = 401;
} else {
await ctx.login(ctx.user, function(err){
console.log("Error:\n- " + err);
})
ctx.body = { user: user }
}
});
await middleware.call(this, ctx, next)
The auth.js are showed below. Also I followed koa-passport example from the author here and tried to use session, but every request i sent will get a TypeError said "Cannot read property 'message' of undefined". But I think this is not the core problem of authentication, but for reference if that really is.
const passport = require('koa-passport')
const fetchUser = (() => {
const user = { id: 1, username: 'name', password: 'pass', isAdmin: 'false' };
return async function() {
return user
}
})()
const LocalStrategy = require('passport-local').Strategy
passport.use(new LocalStrategy(function(username, password, done) {
fetchUser()
.then(user => {
if (username === user.username && password === user.password) {
done(null, user)
} else {
done(null, false)
}
})
.catch(err => done(err))
}))
passport.serializeUser(function(user, done) {
done(null, user.id)
})
passport.deserializeUser(async function(id, done) {
try {
const user = await fetchUser();
done(null, user)
} catch(err) {
done(err)
}
})
module.exports = passport;
By the way when I use the simple default one, it will just give me a "Not found". But through console.log I can see it actually got into the loginPass.
var loginPass = async (ctx, next) =>{
passport.authenticate('local', {
successRedirect: '/myApp',
failureRedirect: '/'
});
};
In server.js:
// Sessions
const convert = require('koa-convert');
const session = require('koa-generic-session');
app.keys = ['mySecret'];
app.use(convert(session()));
// authentication
passport = require('./auth');
app.use(passport.initialize());
app.use(passport.session());
Thanks a lot for any help!!! :D
Related
So I'm trying to make a node.js project with passport authentification. When I log in with correct credentials, login works perfectly. But if I try to log in with wrong password and correct email, instead of redirecting back to login page, the site just keeps on loading indefinetly.
Here is my server.js code:
require('dotenv').config();
const express = require('express');
const app = express();
const passport = require('passport');
const initializePassport = require('./passport-config');
const session = require('express-session');
const methodOverride = require('method-override');
initializePassport(
passport,
async email=>{
try{
let bookshelfUser = await createBookshelfOf("User");
return await new bookshelfUser().where("email", email).fetch().then((data)=>{
return data.attributes;
});
}
catch (e) {
console.log(e);
return null;
}},
async id=>{
try{
let bookshelfUser = await createBookshelfOf("User");
return await new bookshelfUser().where("id", id).fetch().then((data)=>{
return data.attributes;
});
}
catch (e) {
return null;
}});
app.post('/login', passport.authenticate('local', {
successRedirect : '/',
failureRedirect : '/login',
})
);
And this is passport-config
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
function initialize(passport, getUserByEmail, getUserById) {
console.log("Passport initialized");
const authenticateUser = async (email, password, done) => {
const user = await getUserByEmail(email);
if (user == null) {
console.log("No user with that email");
return done(null, false, { message: 'No user with that email' });
}
try {
await bcrypt.compare(password, user.geslo, (err, result)=>{
if (err){
console.log("Password incorrect");
return done(null, false, { message: 'Password incorrect' });
}
if(result){
console.log("User logged in successfully: " + user.username);
return done(null, user);
}
});
} catch (e) {
console.log(e);
return done(e)
}
}
passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser));
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser(async(id, done) => {
return done(null, await getUserById(id));
})
}
module.exports = initialize
I see two possible issues:
you're mixing promises with callbacks for bcrypt.compare(), which may not work (but perhaps it might, I don't know bcrypt too well);
it's not an error when a password doesn't match a hash for bcrypt.compare(); instead, result will be false, and you're not handling that case.
Here's a rewritten version of the block:
try {
const result = await bcrypt.compare(password, user.geslo);
if (! result) {
console.log("Password incorrect");
return done(null, false, { message: 'Password incorrect' });
} else {
console.log("User logged in successfully: " + user.username);
return done(null, user);
}
} catch (e) {
console.log(e);
return done(e)
}
Without passport, I have added simple middleware to authorize user as below.
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const authHeader = req.get('Authorization');
const token = authHeader.split(' ')[1];
let jwttoken;
try {
jwttoken = jwt.verify(token, 'secret');
} catch (err) {
err.statusCode = 500;
throw err;
}
if (!jwttoken) {
const error = new Error('Not authenticated.');
error.statusCode = 401;
throw error;
}
req.userId = jwttoken.userId;
next();
};
with passport, I have added middleware as below
const options = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'SECRET'
}
module.exports = (passport) => {
passport.use(new Strategy(options, async (payload, done) => {
await user.findByPk(payload.userId).then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
}).catch(error => {
return done(null, error);
});
}));
}
Question: like I was adding user to request without passport as req.userId = jwttoken.userId, how can we do it with passport middleware?
At passport this is accomplished with that line of code on your module.exports:
return done(null, user);
This tells passport that the information should be sent to the next function callback at req.user.
So for example, if you "user.findByPk(payload.userId)" response is something like:
{
"name": <NAME>
"profile": <profile>
}
At your protected endpoint's callback, you should see it on req.user.
For example:
app.post('/profile', passport.authenticate('jwt', { session: false }),
function(req, res) {
res.send(req.user.profile);
}
);
With req.user.profile being equal to of the "user.findByPk(payload.userId)" response.
I am new to Node.js.
For my application, I have two different type of users being doctors and patient. Currently, I been coding for the login and registration for patients and it works. I starting to code the login for the doctors and it doesn't seem to be working. Below is the passport.js. I watched a video (https://www.youtube.com/watch?v=6FOq4cUdH8k&ab_channel=TraversyMedia) to learn how to code the login and registration.
I think the issue is within the passport.js, I'm not really sure how to code it, when it comes to the second user.
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
const Patient = require('../models/patients');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (email, password, done) => {
Patient.findOne({
email: email
}).then(patient => {
if (!patient) {
return done(null, false, { message: 'That email is not registered' });
}
bcrypt.compare(password, patient.password, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, patient);
} else {
return done(null, false, { message: 'Password is incorrect' });
}
});
});
})
);
passport.serializeUser(function(patient, done) {
done(null, patient.id);
});
passport.deserializeUser(function(id, done) {
Patient.findById(id, function(err, patient) {
done(err, patient);
});
});
const Doctor = require('../models/doctors');
module.exports = function(passport) {
passport.use(
new LocalStrategy({ usernameField: 'email' }, (demail, dpassword, done) => {
Doctor.findOne({
demail: demail
}).then(doctor => {
if (!doctor) {
return done(null, false, { message: 'That email is not registered' });
}
bcrypt.compare(dpassword, doctor.dpassword, (err, isMatch) => {
if (err) throw err;
if (isMatch) {
return done(null, doctor);
} else {
return done(null, false, { message: 'Password is incorrect' });
}
});
});
})
);
passport.serializeUser(function(doctor, done) {
done(null, doctor.id);
});
passport.deserializeUser(function(id, done) {
Doctor.findById(id, function(err, doctor) {
done(err, doctor);
});
});
}}:
This is my doctor.js in route
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const passport = require('passport');
const Doctor = require('../models/doctors');
router.get('/doctorregister', (req, res) => res.render('Doctorregister'));
router.get('/doctorlogin', (req, res) => res.render('Doctorlogin'));
module.exports = router;
router.post('/doctorregister', (req, res) => {
const { dname, dqualification, dlocation, dpractice, demail, dmobileno, dpassword, dpassword2 } = req.body;
let errors = [];
if (!dname|| !dqualification || !dlocation || !dpractice || !demail || !dmobileno || !dpassword || !dpassword2) {
errors.push({ msg: 'Please enter all fields' });
}
if (dpassword != dpassword2) {
errors.push({ msg: 'Passwords do not match' });
}
if (dpassword.length < 6) {
errors.push({ msg: 'Password must be at least 6 characters' });
}
if (errors.length > 0) {
res.render('doctorregister', {
errors,
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
dpassword2
});
} else {
Doctor.findOne({ demail: demail }).then(doctor => {
if (doctor) {
errors.push({ msg: 'Email already exists' });
res.render('register', {
errors,
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
dpassword2
});
} else {
const newDoctor = new Doctor({
dname,
dqualification,
dlocation,
dpractice,
demail,
dmobileno,
dpassword,
});
//hashing password in the database
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(newDoctor.dpassword, salt, (err, hash) => {
if (err) throw err;
newDoctor.dpassword = hash;
newDoctor.save()
.then(doctor => {
req.flash('success_msg', 'You are now registered and can log in')
res.redirect('/doctors/doctorlogin');
})
.catch(err => console.log(err));
});
});
}
});
}
});
router.post('/doctorlogin', (req,res, next) => {
passport.authenticate('local', {
successRedirect: '/doctordashboard',
failureRedirect: '/doctors/doctorlogin',
failureFlash: true
})(req, res, next);
});
router.get('/logout', (req, res) => {
req.logout();
req.flash('You are logged out');
res.redirect('/doctors/doctorlogin');
});
The two different type of users should be different through the user Schema if you are using mongoose and mongoDB as Travesty-Media uses in some of his videos, and through the permissions in different sections of your web app.
Use the same passport code that you have used for the patient user and it works. Passport takes the user info and turns it into a Bearer auth token and sends it to the client, and then when it receives it from the client it decodes it to see what kind of user is logging in...
Different users have different info but same Passport logic, even if there are 2 different patient-users. So the logic from patient to doctor regarding the Passport is the same.
If you want the patient to have access to different routes of your app, you need to insert a middleware to that route to check if the type of user is the same with what you want it to be, but after passport has got the info from the token it has received from the client side.
passport.authenticate from koa-passport doesn't work with local strategy. I use no sessions, no serializing. It's just simple passport example, but the server response is always 404.
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const passport = require('koa-passport');
const Router = require('koa-router');
const LocalStrategy = require('passport-local').Strategy;
const app = new Koa();
app.use(bodyParser());
app.use(passport.initialize());
passport.use(new LocalStrategy({
session: false,
}, (email, password, done) => {
if(email && password){
return done(null, [email, password]);
}
else {
return done(null, false);
}
}
));
const router = new Router();
router.post('/auth', async (ctx) => {
passport.authenticate('local', { session: false }, (err, user) => {
if(!user)
return ctx.body = 'Failed!';
return ctx.body = { msg: 'Success' };
});
});
app.use(router.routes());
app.listen(3000, console.log('The server is started'));
The answer is in a depth of an issues on github repo of koa-router:
We should return passport.authenticate with passed context object:
router.post('/auth', async (ctx) => {
return passport.authenticate('local', { session: false }, (err, user) => {
if(!user)
return ctx.body = 'Failed!';
return ctx.body = { msg: 'Success' };
})(ctx, next);
});
I had the same issue and understood that the context object must be returned back for it to parse the response.
router.post('/auth', async (ctx) => {
return passport.authenticate('local', { session: false }, (err, user) => {
if(!user)
return ctx.body = 'Failed!';
return ctx.body = { msg: 'Success' };
})(ctx);
});
My signup and login works completely fine until I want to return user to the front end. User ends up being undefined.
Here is my passport.js
const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const User = mongoose.model('users');
passport.serializeUser((user, done) => {
done(null, user._id)
})
passport.deserializeUser((id, done) =>
User.findById(id).then((user) => {
if (user) {
done(null, user)
} else {
}
})
);
passport.use(new localStrategy((username, password, done) => {
User.findOne({ username: username }, (err, user) => {
console.log(user)
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Username not found' });
}
if (!user.comparePassword(password, user.password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}));
Here is my login route:
const passport = require('passport');
const mongoose = require('mongoose');
const User = mongoose.model('users');
module.exports = (app) => {
app.get('/test', (req, res) => {
res.send('elo')
})
app.post('/login',
passport.authenticate('local', {
successRedirect: '/loginSuccess',
failureRedirect: '/loginFailed',
})
);
app.get('/loginSuccess', (req, res) => {
console.log(req.user)
res.send({ success: true, test:'test', user: req.user })
})
app.get('/loginFailed', (req, res) => {
res.send({ success: false, error: "Incorrect credentials" })
})
};
In passport.js the user exists and should be returnet correctly. It's just getting lost somewhere along the way. Whats funnier, I used the exact same code in my other app few months ago and it worked properly.
Edit:
Turns out it works on postman, so I dont know whats going on now