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.
Related
[Update] Question now contains necessary details because of which the author was successful at finding a solution and hence should be reopened for answers.
Question in the title. Is there a way to use connect-flash in the back end (express) so that messages are accessible in the front end react app?
My passport local strategy, I am using a sqlite db:
passport.use(
new LocalStrategy({ usernameField: 'name' },
(name, password, done) => {
//Match user
db.get(`SELECT * FROM Users WHERE name = '${name}'`, (err, user) => {
if(err) throw err;
if(!user) {
return done(null, false, { msg : 'name not registered' });
}
//Match password
bcrypt.compare(password, user.password, (err, isMatch) => {
if(err) throw Error;
if(isMatch) {
done(null, user);
} else {
done(null, false, { msg : 'Password incorrect' })
}
});
})
})
);
and my /login route:
//Login Handle
usersRouter.post('/login', passport.authenticate('local'), (req, res ) => {
res.json('Successfully authenticated.')
}
);
I read that I can add options to this route in an object including
{
failureFlash: true
}
but I am just don't understand how to access the messages (stored in req.flash ?) in my react front end.
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.
I'm trying to secure my sails js rest api with the help of the passport http package but at the moment I can't figure out where the error is in my code.
I used this repo and this tutorial to get an idea of how this should work. My problem is that my code always returns a 401.
I don't really know where to look for the error. If you need more information about my code just comment.
Bruno
EDIT:
I found the source of the problem (With the help of #Viktor). I just didn't really understood how HTTP-Basic authentication works. Now the problem is how do I send my auth-credentials and my data? If I just test the routes with auth(...), they work... But how do I add the data? Or do I have to authenticate me first and send the data in the second request?
passport.js
var passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;
var 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('user-authentication', new BasicStrategy(
function(mail, password, done) {
sails.log.error("hallo");
User.findOne({
mail: mail
}, function(err, user) {
if (err) {
return done(err);
}
if (!user) {
return done(null, false, {
message: 'Incorrect email.'
});
}
// Make sure the password is correct
bcrypt.compare(password, user.password, function(err, isMatch) {
if (err) {
return done(err);
}
// Password did not match
if (!isMatch) {
return done(null, false, {
message: 'Invalid Password'
});
}
// Success
return done(null, user);
});
});
}
));
isAuthenticated.js
var passport = require("passport");
module.exports = function (req, res, ok) {
passport.authenticate("user-authentication", {
session: false
}, function (err, user, info) {
if (err || !user) {
res.set("WWW-Authenticate", "Basic realm=\"Restricted\"");
return res.send("You are not permitted to perform this action", 401);
}
req.session.user = user;
return ok(null, user);
})(req, res, ok);
};
policies.js
module.exports.policies = {
'*': true,
'UserController': {
update: 'isAuthenticated'
}
}
UserController.test.js
var request = require('supertest');
var async = require('async');
describe('UserController', function() {
describe('#new()', function() {
it('...', function (done) {
request(sails.hooks.http.app)
.post('/user/new')
.send({ own_number: '654122', password: 'test', mail: 'test#test.com', device_id: '1234', numbers: [1234567] })
.expect(200)
.end(done);
});
});
describe('#update()', function(){
it('...', function (done) {
async.series([
function(callback){
request(sails.hooks.http.app)
.post('/contact/update')
.send({ number: 1234, mail: "test#test.com", password: "test" })
.expect(200)
.end(callback);
},
function(callback){
request(sails.hooks.http.app)
.post('/user/update')
.send({ numbers: [1234], mail: "tet#test.com", password: "test" })
.expect(200)
.end(callback);
}
], done);
});
});
});
Works for me – I'm able to authenticate as well as access and manage the user data once there's a valid user in the database. With an empty user database, you would get 401 all the time, of course, as these policies don't allow you to create even the first user to authenticate as. Temporarily disabling the UserController policies in config/policies.js gives you the opportunity to create the first user.
Assuming you have at least one valid user in the database, let's narrow down the problem. What output do you get from logging err and user in isAuthenticated.js? If you get null and false, what happens in the different steps in passport.js - are you able to find the user by e-mail address in the database and does the password match?
Do you have custom routes and controller actions or do you use the blueprints?
EDIT: Your update test would look like this with HTTP Basic Authentication:
describe('#update()', function(){
it('...', function (done) {
async.series([
function(callback){
request(sails.hooks.http.app)
.post('/contact/update')
.auth('test#test.com', 'test')
.send({ number: 1234, mail: "test#test.com", password: "test" })
.expect(200)
.end(callback);
},
function(callback){
request(sails.hooks.http.app)
.post('/user/update')
.auth('test#test.com', 'test')
.send({ numbers: [1234], mail: "tet#test.com", password: "test" })
.expect(200)
.end(callback);
}
], done);
});
});
From the documentation here it shows you need userid and password. So looking at your code you have mail in place of userid and that is why you are getting 401 or at least where you can start looking. If you need to verify this you can look at passport-http basic strategy here.
passport.use(new BasicStrategy(
function(userid, password, done) {
User.findOne({ mail: userid, password: password }, function (err, user) {
done(err, user);
});
}
));
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});
I'm using the standard passport local strategy (with express.js) for the signup form on my website. When the failureRedirect is invoked, it redirects back to my signup form correctly, but all the values of my form are wiped blank. I get why this is happening, because of the redirect, but ... This is incredibly annoying for the user if they've made a simple mistake like forgetting a checkbox or their username is already taken. (Also, I know the password should not be sent back to the view). Is there a way to persist the users entered values even after the redirect with passport?
//my route
.post('', passport.authenticate('local-signup', {
failureRedirect: '/account/signup', // redirect back to the signup page if there is an error
failureFlash: true // allow flash messages
}), function(req, res) {
...
});
passport code
passport.use('local-signup', new LocalStrategy({
usernameField: 'username',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
}, function(req, username, password, done) {
process.nextTick(function() {
if(password != params.password2) {
return done(null, false, req.flash('error', 'Passwords do not match.'));
}
User.findOne({
'username': username
}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
if (user) {
return done(null, false, req.flash('error', 'That username is already taken.'));
} else {
... create a new user ...
}
});
});
}));
function(req, username, password, done) {
What if you wrap passport in your route logic. For example,
app.post('/login', function(req, res, next) {
// Do something here with the username & password, like
// checking if the username is available.
if (!username || !password) {
// Render the login form, and pass in the username and password.
res.render('/login', {username: username, password: password});
} else {
// If all is validated, attempt the login:
passport.authenticate('local-signup', {
failureRedirect: '/account/signup',
failureFlash: true
}), function(req, res) {
...handle response here...
}
}
});
I'm not sure if all of that is syntatically correct, but the idea is to do whatever application-specific validation you have to do before you attempt to authenticate with Passport.