I'm working on a project developing an API. The thing is I'm working with Passport and there's this thing that throws me an error of "Missing Credential". I've searched everywhere and I don't know what to do.
Part of Index.js
app.use(express.static(publicc));
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.session());
app.use(cors());
app.use(morgan('dev'));
Passport.js with the SignUp Function
passport.use('signup', new localStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, async (email, password, done) => {
try {
console.log('si')
const user = await findUser(email);
if (user.length > 0) {
return done(null, false, { message: 'User already exists' });
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = {
user_name: req.body.user_name,
user_role: req.body.user_role,
email: email,
user_password: hashedPassword
};
console.log(newUser);
const result = await createUser(newUser);
return done(null, newUser);
}
catch (error) {
console.log('si')
done(error);
}
}
));
Controller with the SingUp
const postSignup = async (req, res, next) => {
passport.authenticate('signup', { session: false }, async(err, user, info) => {
try{
console.log(req.body);
if(err){
const error = new Error('An error occurred');
return next(error);
}
const message = info.message;
if(!user){
res.status(401).json({ success: false, message });
return
}
return res.json({succes: true, message});
}catch(error){
return next(error);
}
})(req, res, next);
};
I already tried adding the body parsing and nothing changed. I've searched every forum but still no answer.
I see you are saving email and password at DB, but you don't save salt to compare recieved password from user on sign in with that you have saved at DB.
https://en.wikipedia.org/wiki/Salt_(cryptography)
You steps to auth.
Get user by email from database.
Check that hashed password that you recieved from user + salt saved at database for this user equals hashed password at your database. Or just remove salt, don't hash password (but it strongly not recommended), after it will be work, try to complicate the task and hash passwords without salt. After it will be works, try to add salt.
Look at this example how to use passport with local strategy + Express.
https://github.com/jaredhanson/passport-local/wiki/Examples
Related
After authentication (with passport module), a template was rendered with the req.user object as follow,
app.get('/', (req, res) => {
console.log(`Router get user: ${req.user}`);
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
I checked the req.user by console.log, and the user object was printed as expected,
Router get user: {
_id: new ObjectId("629e3821bfb2869c42ac3c4b"),
username: 'me',
password: '123'
}
The second console.log showed the type of req.user is object,
Router get user of type: object
After convert the req.user to a string,
app.get('/', (req, res) => {
console.log(JSON.stringify(req.user));
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
the output became,
{"_id":"629e3821bfb2869c42ac3c4b","username":"me","password":"123"}
Router get user of type: object
If I further log req.user.username as follow,
app.get('/', (req, res) => {
console.log(req.user.username);
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
I got the error,
TypeError: Cannot read properties of undefined (reading 'username')
at /Users/Wei/github/play-js/express/authentication/src/app.js:87:24
But when I used user.username in the template file, it didn't show the username.
<body>
{{#if user}}
<h1>WELCOME BACK {{user.username}}</h1>
{{/if}}
</body>
But when I replace the {{user.username}} by {{user}}, the user object was printed correctly,
<body>
{{#if user}}
<h1>WELCOME BACK {{user}}</h1>
{{/if}}
</body>
WELCOME BACK { _id: new ObjectId("629e3821bfb2869c42ac3c4b"), username: 'me', password: '123' }
But according to the Handlebars Doc, Handlebars expression CAN be dot-separated paths.
So what's the problem here?
Here's the complete code how I set up the express server and passport authentication,
// connect to MongoDB
const mongoDB = process.env.DB_URI;
mongoose.connect(mongoDB);
const db = mongoose.connection;
db.on('error', console.error.bind(console), 'MongoDB connection error');
// Schema & Model
const userSchema = new Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', userSchema);
// Express server
const app = express();
app.set('views', path.join(__dirname, 'views'));
const eh = handlebars.create(); // ExpressHandlebars instance
app.engine('handlebars', eh.engine); // register the engine() function
app.set('view engine', 'handlebars');
// Middleware
app.use(morgan('dev')); // logger
app.use(session({
secret: 'cats',
resave: false,
saveUninitialized: true,
}));
app.use(passport.initialize());
app.use(passport.session()); // this middleware will set cookie in client computer for each session.
app.use(express.urlencoded({
extended: false,
}));
// Verify username & password in our database
// Register the LocalStrategy to the passport.
passport.use(
new LocalStrategy(function verify(username, password, done) {
User.findOne({username: username}, (err, user) => {
if (err) return done(err);
if (!user) return done(null, false, {message: 'Incorrect username'});
if (user.password !== password) return done(null, false, {message: 'Incorrect password'});
return done(null, user);
});
})
);
// Only store user._id in the cookie.
passport.serializeUser(function(user, done) {
console.log(`serialize: ${user._id}`);
done(null, user._id);
});
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
console.log(`deserialize search for: ${_id}`);
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
});
});
// router
app.get('/', (req, res) => {
console.log(JSON.stringify(req.user));
console.log("Router get user of type: " + (typeof req.user));
res.render('index', {
layout: false,
user: req.user,
});
});
app.post('/log-in', passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/',
}));
With the help of #Phil, we found the bug in deserializeUser() function.
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
});
});
passport.deserializeUser() simply extracts user._id stored in cookies, and search for entire user object from MongoDB, which will be lately set as our req.user.
The problem is that User.findById() return a mongoose Document object, which doesn't have its own username property.
What we need here is a plain javascript object. We can achieve that by enable the lean option right after the findById() function.
// Get the user object from database by searching user._id.
passport.deserializeUser(function(_id, done) {
User.findById(_id, function(err, user) {
console.log(`deserialization find user: ${user}`);
done(err, user);
}).lean(); // it returns a plain javascript object now
});
I have been building this project from a tutorial. The signup functionality works fine but the login feature doesn't work. Whenever I try logging in a registered user using postman the error I get is
Error: Unknown authentication strategy "local"
In the other posts on stack overflow, I didn't find a solution to this error. Passport, passport-local and passport-jwt are all installed so that shouldn't be the issue. I would really appreciate any sort of help.
passport.js
require('dotenv').config();
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;
const User = require('./models/User');
// Environment variables
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const cookieExtractor = req => {
let token = null;
// Retrieve the token from cookies
if (req && req.cookies) {
token = req.cookies['access_token'];
}
return token;
};
const jwtOptions = {
jwtFromRequest: cookieExtractor,
secretOrKey: STRATEGY_KEY,
};
// Authorization for protected routes
passport.use(
new JWTStrategy(jwtOptions, (payload, done) => {
User.findById({ _id: payload.sub }, (err, user) => {
// Check for error
if (err) return done(err, false);
// Check if user exists
if (user) return done(null, user);
return done(null, false);
});
})
);
// Local strategy using username and password
passport.use(
new LocalStrategy((username, password, done) => {
User.findOne({ username }, (err, user) => {
// Error while fetching the user from database
if (err) return done(err);
// No such user exists
if (!user) return done(null, false);
// Check if entered password matches
user.comparePassword(password, done);
});
})
);
routes.js
require('dotenv').config();
const express = require('express');
const passport = require('passport');
const router = express.Router();
const STRATEGY_KEY = process.env.STRATEGY_KEY;
const signToken = userID => {
return jwt.sign(
{
iss: STRATEGY_KEY,
sub: userID,
},
STRATEGY_KEY,
{
expiresIn: '1h',
}
);
};
router.post(
'/signin',
passport.authenticate('local', { session: false }),
(req, res) => {
if (req.isAuthenticated()) {
const { _id, username, email } = req.user;
const token = signToken(_id);
res.cookie('access_token', token, {
httpOnly: true,
sameSite: true,
});
res.status(200).json({
isAuthenticated: true,
user: {
username,
email,
},
});
}
}
);
module.exports = router;
So after many hours of debugging, the solution I found to this problem was that I didn't import passport.js file in routes.js file, which I was not expecting since that import stays there ideal not doing anything, not being part of any code(exceot the import) but I was wrong. The passport configuration we make in that file is imported under the hood even though it doesn't take part in any further lines of that file.
I am trying to get mongoose return data from local MongoDB instance. Eveything else is running fine. The form is giving data to passport. I have also inserted data into mongodb manually. The only error is in user.findOne. When I print user into console, instead of returning some data it returns null
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/member');
// authentication using passport
passport.use(new LocalStrategy({
usernameField: 'user',
passwordField: 'password'
},
function (username, password, done) {
User.findOne({ user: username }, function (err, user) {
// console.log(user, username, password);
if (err) {
console.log(`Error in configuring passport-local \n ${err}`);
return done(err);
}
if (!user) {
console.log(`Invalid username or password!!`)
return done(null, false);
}
return done(null, user);
});
}
));
My schema:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
user: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
dept: String,
yr: Number,
name: {
type: String
}
}, {
timestamps: true
});
module.exports = mongoose.model('member_list', userSchema, 'member_list');
Here's my full code: https://github.com/krush11/site
Let me know if you need any more info
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/member');
// authentication using passport
passport.use(new LocalStrategy({
usernameField: 'user',
passwordField: 'password'
},
async function (username, password, done) {
await User.findOne({ user: username }, function (err, user) {
// console.log(user, username, password);
if (err) {
console.log(`Error in configuring passport-local \n ${err}`);
return done(err);
}
if (!user) {
console.log(`Invalid username or password!!`)
return done(null, false);
}
return done(null, user);
});
}
));
The function looks good but try making it asynchronous then await the query from the database. Take a look at the modification I made to your code. You might also want to refactor your code and use promises instead of callback and wrap everything inside of try{} catch{} block.
But making the function asynchronous and awaiting the query will fix the issue, this is because the findOne() method actually returns a query and might not have completed before you console it.
Let me know if you still have any issue.
I'm trying to get the user to be able to sign up to my website, and store the credentials on mongodb.
This is my auth.js file, where the route is defined:
router.post('/signup', (req,res,next) => {
Passport.authenticate('local-signup', err => {
if (err) {
if (err.name === "MongoError" && err.code === 11000) {
res.status(409).json({
success: false,
message: "Unsuccessful",
errors: {
email: "This email is already taken."
}
});
}
res.status(400).json({
success: false,
message: "Unsuccessful",
errors: {
unknown: "Could not process for some reason. Contact admin."
}
});
}
res.status(200).json({
success: true,
message: "Successful",
errors: {}
});
}) (res, req, next);
}
That last bracket got a bit messed up but believe me, it's not a syntax error.
This snippet is where I have defined the passport strategy:
require ('../Models/Users')
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/onlinestore');
const User = mongoose.model('User');
const PassportLocalStrategy = require('passport-local').Strategy;
const Passport = require('passport');
module.exports = Passport.use(new PassportLocalStrategy({
usernameField: 'email',
passwordField: 'password',
session: false,
passReqToCallback: true,
}, (email, password, done) => {
let user = new User();
user.email = email.trim();
user.password = password.trim();
user.save(err => {
if (err) {
console.log(err);
return done(err);
} else {
console.log("Success");
return done(null);
}
});
}
));
The route is able to get the user inputted password and user. When I click submit, literally nothing happens; the server doesn't console anything nor does the client. After a bit of debugging I think the issue is due to the fact that Passport.Authenticate is not being called but I'm not sure why. I can post other code snippets if necessary, thanks!
This is OP. I was able to find the solution.
1) In my auth.js file,
Passport.authenticate('local-signup', err => {
replace the 'local-signup' with 'local'
so it becomes:
Passport.authenticate('local', err => {
2) I happened to have multiple Passport strategies and each are in their own file, (one for login and one for signup). To use the correct "local" I have to import the correct one in my auth.js file. Easy enough, it's
const passport = require('../Passport/local-signup');
For login, it would be a different file.
When I send a request via axios to my Express app, req.isAuthenticated() is always false and req.user does not exist, even after logging in. But when I send a request via Postman to the app, it works. It seems that deserializeUser() is never called so the req.session.passport field is never populated.
I've tried all of the suggestions online, any help is appreciated.
External request:
async tweet(content) {
try {
await axios.post(this.url + '/tweets/new', {
content: content,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
}
});
} catch (err) {
console.log(err);
}
}
index.js
const passport = require('passport');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const RedisStore = require('connect-redis')(session)
const redisCookie = require('heroku-redis-client');
require('./config/passport')(passport);
// required for passport
app.use(cookieParser());
app.use(session({
// secret: process.env.SECRET || 'enteryoursecrethere',
secret: 'enteryoursecrethere',
cookie: { maxAge: 3600000 },
resave: true,
store: new RedisStore({client: redisCookie.createClient()}),
saveUninitialized: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
next();
});
router.js
var tweets = require('../controllers/tweets');
var router = express.Router();
var isLoggedIn = require('../middleware/isLoggedIn');
router.post('/tweets/new', isLoggedIn, tweets.tweet);
middleware/isLoggedIn.js
module.exports = (req, res, next) => {
// If user is authenticated in the session, carry on.
if (req.isAuthenticated()) {
next();
return
}
// If they aren't redirect them to the home page.
res.redirect('/');
}
passport.js
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models').User;
const Sequelize = require('sequelize');
module.exports = function(passport) {
// The login request establishes a session maintained in a browser cookie.
// Requests after the login request not contain credentials,
// but rather the unique cookie that identifies the session. The user object
// is constructed to and from the ID in the cookie.
// Converts user to user id.
passport.serializeUser(function(user, done) {
done(null, user.id);
});
// Converts user id to user, stored in req.user.
passport.deserializeUser(function(id, done) {
User.findById(id).then(function(user) {
done(null, user);
}).catch(function(err) {
done(err);
});
});
/* ============Login============ */
passport.use('local-login', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback : true // Send entire request for flash message.
}, loginCallback));
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback : true
}, signupCallback));
};
function loginCallback(req, username, password, done) {
if (req.isAuthenticated()) {
return done(null, req.user);
}
// Look up the user by username.
User.findOne({
where: {
username: username
}
}).then(function(user) {
if (!user) {
return done(null, false, req.flash('loginUsernameMessage', 'Wrong username.'));
}
if (!user.validatePassword(password)) {
return done(null, false, req.flash('loginPasswordMessage', 'Wrong password.'));
}
return done(null, user.get());
}).catch(function(err) {
return done(err);
});
}
function signupCallback(req, username, password, done) {
// Asynchronous. User.findOne wont fire unless data is sent back.
process.nextTick(function() {
if (password != req.body.password_confirm) {
return done(null, false, req.flash('signupMessage', 'Passwords don\'t match.'));
}
// Find a user whose email is the same as the forms email.
// We are checking to see if the user trying to login already exists.
User.findOne({
where: {
[Sequelize.Op.or]: [ { username: username }, { email: req.body.email }]
}
}).then(function(user) {
// Check to see if theres already a user with that username or email.
if (user) {
return done(null, false, req.flash('signupMessage', 'That email or username is already taken.'));
}
// Create the user.
var data = {
fname: req.body.fname,
lname: req.body.lname,
username: username,
email: req.body.email,
password: User.generateHash(password)
}
User.create(data).then(function(newUser) {
return done(null, newUser);
}).catch(function(err) {
return done(err);
});
}).catch(function(err) {
return done(err);
});
});
}
It works only with postman because the session is being set at the server(express) but not at the client(axios).
So when you request from axios, It doesn't know if the session is already set at the server. So you need to send the credentials origin along with the request. Modify your request like this:
await axios.post(this.url + '/tweets/new', {
content: content,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
credentials: "same-origin"
});
Refer this for more info.
This question is a year old now but I had a heck of a time solving this problem using Node/Express/Passport/React with a local-strategy and express-session.
For me, adding this to the header:
withCredentials:true
wasn't enough. That did trigger a CORS problem but did not actually make axios send the session cookie with the request.
This causes a situation where the login would create a session, but the deserialize function would never be called and req.user would always be empty.
This is what fixed the issue:
In my React component constructor I added this line of code:
axios.defaults.withCredentials = true;
That fixed it.
In fact, it fixed it so well that I was able to remove all other headers in Axios. Everything just started working.
Figuring this out took me a solid two hours today, I hope this answer helps someone.