Why doesn't my custom callback in Passport JS work? - javascript

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.

Related

Error: Unknown authentication strategy "local" while trying to signin

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.

Server throwing 500 error on certain route

I've been doing some small school project on making our own API and connecting it to an angular front end.
I've been following guide on things and I've came across the problem where my app started throwing internal server error 500 after implementing controllers.
It all worked fine until I've imported the controllers for user registration.
Posts controller works just fine, so does the login part of the ap
I tried logging the errors but it wouldnt output anything.
Here is my code:
user route
const express = require("express");
const UserController = require("../controllers/user");
const router = express.Router();
router.post("/signup", UserController.createUser);
router.post("/login", UserController.userLogin);
module.exports = router;
User controller
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const User = require("../models/user");
exports.createUser = (req, res, next) => {
bcrypt.hash(req.body.password, 10).then(hash => {
const user = new User({
email: req.body.email,
password: hash
});
user
.save()
.then(result => {
res.status(201).json({
message: "User created!",
result: result
});
})
.catch(err => {
res.status(500).json({
message: "Invalid authentication credentials!"
});
});
});
}
exports.userLogin = (req, res, next) => {
let fetchedUser;
User.findOne({ email: req.body.email })
.then(user => {
if (!user) {
return res.status(401).json({
message: "Auth failed"
});
}
fetchedUser = user;
return bcrypt.compare(req.body.password, user.password);
})
.then(result => {
if (!result) {
return res.status(401).json({
message: "Auth failed"
});
}
const token = jwt.sign(
{ email: fetchedUser.email, userId: fetchedUser._id },
"b9SNz3xg9gjY",
{ expiresIn: "1h" }
);
res.status(200).json({
token: token,
expiresIn: 3600,
userId: fetchedUser._id
});
})
.catch(err => {
return res.status(401).json({
message: "Invalid authentication credentials!"
});
});
}
I am expecting to be able to register an account which will be able to post new posts. It all worked just fine until I've made controllers and moved the requests and functions into controller file.
I really apologize for asking this probably simple question, but my programming skills are still low
In userLogin, when the user doesn't exist, you return res.status(401)..., which is chained to the next .then call as result (instead of your expectation that it would be the value returned by bcrypt.compare).
What you can do is instead of:
if (!user) {
return res.status(401).json({
message: "Auth failed"
});
}
try
if (!user) {
throw new Error("Auth failed");
}
which will be caught in your .catch.

Proper json responses when using passport.js?

I'm using Passport.js for authentication in an Express 4 API. Mostly everything works fine but I'm finding that sending proper json responses i.e error messages or objects is difficult with passport. For example this is my LocalStrategy for loggin in:
passport.use (
'login',
new LocalStrategy (
{
usernameField: 'email',
},
(email, password, done) => {
User.findOne ({email: email}, (err, foundUser) => {
if (err) return done (err);
if (!foundUser) {
return done (null, false, {
message: 'Invalid Username.',
});
}
if (!foundUser.comparePassword (password)) {
return done (null, false, {
message: 'Invalid Password.',
});
}
return done (null, foundUser);
});
}
)
);
I'm setting custom messages for when the authentication fails, but these messages never show up in my api responses. How do I send messages like these if something goes wrong during authentication?
app.post (
'/signup',
passport.authenticate ('signup', {
successRedirect: '/user',
failureMessage: true,
successMessage: true,
})
);
Moreover, Instead setting redirects like successRedirect I want to send proper json responses for each case and if an error occurs I want to send that as a json object instead of redirecting to a route. How can I do this?
Try to use async await here.
passport.use(
'login',
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
const user = await User.findOne({ email });
// now if user exists it will give the whole document if not then null, now you can test your conditions based on this user flag /
} catch (error) {
done(error);
}
},
),
);
for the last case you mentioned, you can send the response as
app.post('/signup', passport.authenticate('login'), (req, res) => {
res.status(200).json({
message: 'Your message here',
user: req.user,
});
});
here it should be login in passport.authenticate('login') as you are using local strategy by using login in above code.

Passport JS Custom callback called 3 times

I'm building an express js api with passport js, and in order to be able to return custom error messsages formatted as json I'm using custom callbacks.
When I provide an unknown email the custom callback I wrote is called 3 times, resulting in Unhandled rejection Error: Can't set headers after they are sent.. Which makes sense.
Any help is appreciated.
Here is my implementation:
Strategy:
const localLoginStrategy = new LocalStrategy({
usernameField: "emailAddress"
}, (emailAddress, password, done) => {
// Called once
User.findOne({
where: { emailAddress }
}).then((existingUser) => {
// Called once
if (!existingUser) { return done(null, false, { message: "Invalid email/password combination", status: 401 }); }
return existingUser.comparePassword(password);
}).then((userData) => {
return done(null, userData);
}).catch((err) => {
return done(null, false, { message: "Invalid email/password combination", status: 401 });
});
});
passport.use(localLoginStrategy);
Express middleware for authentication using custom callback:
const requireUsernamePassword = (req, res, next) => {
if(!req.body.emailAddress || !req.body.password) {
return res.status(400).json({ message: "No emailAddress and/or password provided" });
}
// Called once
passport.authenticate("local", { session: false }, (err, user, info) => {
// Called three times!
console.log("authenticate callback")
if (!user || err) {
return res
.status(info.status || 400)
.json({ message: info.message || "Authentication error" });
}
req.user = user;
return next();
})(req, res, next);
};
To check your mandatory request body fields create one generic middleware that will check required field and return appropriate return code. Just like below.
module.exports = function checkParams(params) {
params = params || [];
return function(req, res, next) {
var valid = true;
if(Array.isArray(params)) {
params.forEach(function(_param) {
valid = valid && !!req.body[_param];
});
}
if (valid) { next() } else {return res.status(400).end();} //this is for missing required parameters
};
};
Now lets say for example you have two APIs. Login and CreateUser. API routes should looks like below
app.post('/Login', checkParams(['emailAddress', 'password']), passport.authenticate('local', { failureRedirect: '/login' }), actualLoginMethod);
app.post('/CreateUser', checkParams(['userName', 'Phone']), passport.authenticate('local', { failureRedirect: '/login' }), actualCreateUserMethod);
If either of these parameter (userName and Phone in /CreateUser + emailAddress and password in /Login) is missing then it will return 400 status and stop execution from that point, you may change the checkParams's logic as per your need.
If required parameters are available then it will check JWT local strategy. Once request is through to both the check points then it will call actual method.
Hope this might help you.
You are calling the done function multiple times.
I believe when you call return done(...) in the then method, the next then will call done again.
So that's why your callback function from requireUsernamePassword has been called more then one time.
Hope it helps.

Display passport.js authentication error message in view

I have a new Sails.js project using Passport.js to authenticate users. I have the basic authentication working (meaning a user can sign up and successfully log in), but would like to display the appropriate error message in the login view if they don't enter the correct credentials. I can't figure out how to print any error messages in the view.
Here's my setup. I have config/passport.js, which contains the following:
var passport = require('passport'),
LocalStrategy = require('passport-local').Strategy,
bcrypt = require('bcrypt');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findOne({ id: id } , function (err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback : true
},
function(req, email, password, done) {
User.findOne({ email: email }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Please enter a valid email address.' });
}
if (!req.body.username) {
return done(null, false, { message: 'Please enter your username.' });
}
bcrypt.compare(password, user.password, function (err, res) {
if (!res) {
return done(null, false, {
message: 'Invalid Password'
});
}
var returnUser = {
username: user.username,
email: user.email,
createdAt: user.createdAt,
id: user.id
};
return done(null, returnUser, {
message: 'Logged In Successfully'
});
});
});
}
));
Then I have api/controllers/AuthController.js, which contains the following:
var passport = require('passport');
module.exports = {
_config: {
actions: false,
shortcuts: false,
rest: false
},
login: passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true
}),
logout: function(req, res) {
req.logout();
res.redirect('/login');
}
};
Again, this is working properly if the user fills in their correct credentials. I'm using Handlebars as my templating engine, and would like to display the error message in the login view like so:
<div class="alert">{{ message }}</div>
So far I've tried {{ failureMessage }} {{ message }} {{ req.flash.failureMessage }} {{ req.flash.err }} {{ req.flash.message }} to no avail. So all this to say, how do I display the appropriate error message in my view? And better yet, how would I highlight the errant field in my view?
At first glance, looks like sails is using Express 3.0. Per the Passport docs (http://passportjs.org/docs), you will need to explicitly add middleware to support flash (they recommend https://github.com/jaredhanson/connect-flash).
Not a passport expert here, but according to this all you need to do is to re-render the login view with req.flash('error'):
res.render("login", {error: req.flash("error")});
And then in the handlebars template, display the error:
{{ error }}
Don't know if this might help you, Brad.
https://stackoverflow.com/a/25151855/3499069
I think in your serializeUser call you need it to be user[0].id, but I've moved away from Passport recently, so I could be wrong.
In my opinion you didn't include express flashes ?
var flash = require('connect-flash');
app.use(flash());
in your router add
req.flash(type, message);
and using
var t = req.flash(type);
res.render(view, {flashMessage: t});

Categories

Resources