I have been looking at some passport.js tutorials online but haven't grasped a clear understanding of what is happening. Can someone help me clear my doubts below? Please read the paragraph at the bottom first.
So assuming I set up everything correctly, this is the login strategy:
passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function(req, username, password, done) {
// check in mongo if a user with username exists or not
User.findOne({ 'username' : username },
function(err, user) {
// In case of any error, return using the done method
if (err)
return done(err);
// Username does not exist, log error & redirect back
if (!user){
console.log('User Not Found with username '+username);
return done(null, false,
req.flash('message', 'User Not found.'));
}
// User exists but wrong password, log the error
if (!isValidPassword(user, password)){
console.log('Invalid Password');
return done(null, false,
req.flash('message', 'Invalid Password'));
}
// User and password both match, return user from
// done method which will be treated like success
return done(null, user);
}
);
}));
Now in my app.js (server) I have this as one of my routes:
/* Handle Login POST */
router.post('/login', passport.authenticate('login', {
successRedirect: '/home',
failureRedirect: '/',
failureFlash : true
}));
Now in my AJS file:
app.controller('loginController', function($scope) {
var user = $resource('/login');
$scope.createUser = function() {
var User = new user();
User.username = $scope.usernameVar;
User.password = $scope.passwordVar;
User.save();
}
});
Please read this first (Instead of going through the code first):
So when the user clicks on the login button on the login page the createUser function above is run (in my AJS file). Then I create a resource object for the endpoint '/login' and when I call save on that it will run the route for that '/login' endpoint on my server (app.js). Then in my server it will passport.authenticate('login', ... which will run the passport middleware.
Now my question is:
In the passport.use('login'... strategy where the do values for the variables req, username, and password come from in the callback to that strategy. Do I have to explicitly pass the username and password the user enters in the textfield on my front end. Like I have a two way data binding for those two textfields in AJS view. If so how do I pass those username and password values?
Do these two lines in my AJS controller User.username = $scope.usernameVar; and User.password = $scope.passwordVar; attach the usernameVar and passwordVar values to the req object on my server for the route '/login'?
If you have a form with action to post to your path /login, and have input names labeled after your username and password, the submit button will pass the values along to your passport code.
Check out Form in the docs.
Related
How can i directly authenticate the user after signup.
Below are the the deatail of serializeUser and deserializeUser.
passport.serializeUser(function(user, done) {
done(null, {tutorId: user.tutorId, userType: user.userType});
});
passport.deserializeUser(function(userData, done) {
Tutor.getTutorById(userData.tutorId, (err, user) => {
if (err) {
try {
logger.silly(`message: POST inside passport.deserializeUser; file: index.js; error: ${err}; user: ${JSON.stringify(user)}`);
logger.error(`message: POST inside passport.deserializeUser; file: index.js; error: ${err}; user: ${JSON.stringify(user)}`);
} catch (e) {
You can use req.login() after successful registration.
From official Passport documentation:
Note: passport.authenticate() middleware invokes req.login()
automatically. This function is primarily used when users sign up,
during which req.login() can be invoked to automatically log in the
newly registered user.
A sample registration code might look like this:
router.post("/register",(req,res) => {
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
//define other things here
//create hash and salt here
user.save().then(user => {
//on successfult registration
//login user here, using req.login
req.login(user ,err => {
if(!err){
//redirect to logged-in page
//or user page
res.redirect('/')
}
})
})
})
Read about req.login() in official passport documentsation
I hope this helps you out.
you can create token just after successful registration and send it back in registration response
I have local passport strategies set up to login and signup users, and I the way the current userflow works is that the user visits the signup page, enters their email address and password, and can login with those exact same credentials in the login view. Nothing fancy.
What I've noticed is that when I attempt to change the single line name attribute of the input field from 'username' to something like, 'email' to make it more readable, the code breaks with the message "Invalid user credentials" propagating via connect-flash. Every proof of concepts I've seen has had that input information be 'username', but when I change that single line and enter a distinct new email, signup no longer works.
Here is the view in question:
views/signup.ejs
<h1><span class="fa fa-sign-in"></span> Signup</h1>
<!-- show any messages that come back with authentication -->
<% if (message.length > 0) { %>
<div class="alert alert-danger"><%= message %></div>
<% } %>
<!-- LOGIN FORM -->
<form action="/users/signup" method="post">
<div class="form-group">
<label>Email</label>
<!-- the line directly below works -->
<input type="text" class="form-control" name="username">
<!-- but this line immediately below does not work! -->
<!-- <input type="text" class="form-control" name="email"> -->
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-warning btn-lg">Signup</button>
</form>
Here is my local-signup strategy:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
db.User.findById(id).then(function(user) {
done(null, user);
})
.catch(function(err){
done(err, null);
});
});
passport.use('local-signup', new LocalStrategy({
emailField : 'email',
passwordField : 'password',
passReqToCallback : true },
function(req, email, password, done){
if(req.session.user && req.session){
done(null, false, {message: 'User is already logged in, signup illegal'});
}
else{
db.User.findOne({ where: {email : email }})
.then(function(user) {
if(user !== null) {
done(null, false, {message: 'User already exists'});
}
else{
//create user yada yada
}
})
.catch(function(err){
return done(null, false, {message: err});
});
}
}
));
Is it absolutely required by passport.js that the attribute in the signup view for the signup strategy MUST be username? That strikes me as incredibly strange. Again, I'd like to emphasize the fact that literally the only change I made to the existing functional signup process was to change the attribute to 'email' instead of 'username'.
Though similar question have been asked a couple of times, they are without a really good answer in the context that PassportJS is to make authentication simple and so we can't expect those who are implementing it to delve deep into its code.
Passport-local is the Strategy that most people will utilize when using PassportJS. It typically expects a value of username and email as mentioned above.
If you go into node_modules/<path to passport-local>/strategy.js you will find more documentation on how to implement this.
`Strategy` constructor.
The local authentication strategy authenticates requests based on the
credentials submitted through an HTML-based login form.
Applications must supply a `verify` callback which accepts `username` and
`password` credentials, and then calls the `done` callback supplying a
`user`, which should be set to `false` if the credentials are not valid.
If an exception occured, `err` should be set.
Optionally, `options` can be used to change the fields in which the
credentials are found.
Options:
- `usernameField` field name where the username is found, defaults to _username_
- `passwordField` field name where the password is found, defaults to _password_
- `passReqToCallback` when `true`, `req` is the first argument to the verify callback (default: `false`)
The example that Jared Hanson, provided here that follows, is frankly inadequate as it doesn't even implement any of these features, thus, I've ommitted it, but if you look at the actual Strategy constructor's code, it's much clearer.
function Strategy(options, verify) {
if (typeof options == 'function') {
verify = options;
options = {};
}
if (!verify) { throw new TypeError('LocalStrategy requires a verify callback'); }
this._usernameField = options.usernameField || 'username';
this._passwordField = options.passwordField || 'password';
passport.Strategy.call(this);
this.name = 'local';
this._verify = verify;
this._passReqToCallback = options.passReqToCallback;
}
So, basically, it checks to see if you passed in the callback appropriately, and checks to see if you passed in an options object. This options object is then checked to see for configurations and it does so via short-circuiting with the || symbol. So if you don't pass in any configs it'll just default to the username and password params.
Thus, pass inside your local config as follows -- in this example I swap out username for email.
passport.use('local-signup', new LocalStrategy({
usernameField: 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done){
if(req.session.user && req.session){
done(null, false, {message: 'User is already logged in, signup illegal'});
}
else{......
Finally, go into your model or database schema and make sure to change columns appropriately to match.
Extra info:
"Why do we need to configure PassportJS?" (answering because that same question occurred to me)
The answer is you need to configure it to your database needs; you might be using MySQL, or MongoDB or some other database and each PassportJS implementation has to suit that database somehow. The following are questions that PassportJS cannot figure out on its own without customization from the user.
Do I need to use an ORM?
What kind of error messages do I need to display?
What other fields do I need to accept?
TL;DR
pass in an options object to ur new local strategy config by changing expected params around as intended by the passport-local creator.
You need to tell Passport that you're going to use the email field as the username.
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
db.User.findById(id).then(function(user) {
done(null, user);
})
.catch(function(err){
done(err, null);
});
});
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
},
function(req, email, password, done){
if(req.session.user && req.session){
done(null, false, {message: 'User is already logged in, signup illegal'});
}
else{
db.User.findOne({ where: {email : email }})
.then(function(user) {
if(user !== null) {
done(null, false, {message: 'User already exists'});
}
else{
//create user yada yada
}
})
.catch(function(err){
return done(null, false, {message: err});
});
}
}
));
You can use a username in conjunction with an email by getting the field from the body.
req.body.username;
I have an app that uses the MEAN stack, recently I have seen a little strange behaviour. Now this doesn't happen every time a user registers, so far it has happened 3 times. When a user registers the app creates 2 accounts for that user with all the same details. Now I have already added functionality to detect if a user already exists with that email and redirect them towards the login page but doesnt seem to stopping the issue.
Heres my code:
// =========================================================================
// LOCAL SIGNUP ============================================================
// =========================================================================
// we are using named strategies since we have one for login and one for signup
// by default, if there was no name, it would just be called 'local'
passport.use('local-signup', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
firstNameField: 'firstName',
lastNameField: 'lastName',
usernameField: 'email',
passwordField: 'password',
jobTitleField: 'jobTitle',
startDateField: 'startDate',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
// 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({'email': email}, function(err, user) {
// if there are any errors, return the error
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, {
message: 'That email is already taken.'
});
}
else { var token = crypto.randomBytes().toString();
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.firstName = req.body.firstName;
newUser.lastName = req.body.lastName;
newUser.email = email;
newUser.password = newUser.generateHash(password); // use the generateHash function in our user model
newUser.jobTitle = req.body.jobTitle;
newUser.startDate = req.body.startDate;
newUser.birthday = req.body.birthday;
newUser.region = req.body.region;
newUser.sector = req.body.sector;
newUser.accountConfirmationToken = token;
newUser.accountConfirmationTokenExpires = Date.now() + 3600000;
newUser.accountVerified = 'false';
newUser.isLineManager = 'false';
// save the user
newUser.save(function(err) {
if (err)
throw err;
else {
var data = {
from: 'system',
to: email,
subject: 'Account Verification',
text: 'You recently registered onto the App, to gain access to your account please verify your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/verify/' + token + '\n\n'
};
mailgun.messages().send(data, function(error, body) {
console.log(body);
console.log("setting token 1");
req.flash('info', 'An e-mail has been sent to ' + email + ' with further instructions.');
});
return done(null, newUser);
}
});
}
});
}));
My Conclusions:
I tested the app by creating a test account and once I had filled out the signup form I quickly clicked twice on the signup-now button and when I checked the database it had created 2 accounts with the same details. Basically it sends 2 POST requests to create accounts and both of them get approved. When only 1 should be approved.
My Question:
How can I fix this issue so if the user clicks twice on the signup
button it only creates one account.
Also could there be another reason this might be happening, is there
any issue with the code above?
Thanks.
Edit:
App Config Code:
// configuration ===============================================================
mongoose.connect(database.url); // connect to mongoDB database on modulus.io
require('./config/passport')(passport);
app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/views')); // set the static files location /public/img will be /img for users
app.use(busboy());
app.use(compression()); //use compression
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.urlencoded({'extended': true})); // parse application/x-www-form-urlencoded
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(methodOverride());
app.use(cookieParser()); // read cookies (needed for auth)
app.set('view engine', 'ejs'); // set up ejs for templating
// required for passport
app.use(session({ secret: ''})); // session secret
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
app.use(flash()); // use connect-flash for flash messages stored in session
Edit:
Route code:
app.post('/signup', function(req, res, next) {
passport.authenticate('local-signup', function(err, user, info) {
if (err) {
return next(err);
}
if (!user) {
return res.json({
message: 'An account with this email is already registered. Please try to login, if you cant remeber the password then please use our password reset service'
})
}
req.logIn(user, function(err) {
if (err) {
return next(err);
}
return res.json({
redirectUrl: '/verifyAccount',
message: 'We have sent an email to verify your email. Once you have verified your account you will be able to login'
});
});
})(req, res, next);
});
You can disable the signup button on first press to prevent double click on it.
As a solution I can advice you to set unique constraint on your email column, that won't to allow to insert rows with an existing email
And what about the code that uses LocalStrategy, it should work correctly, but you can just override usernameField, passwordField(So, you can remove other fields). Use req.body in order to get other form data as you've already done (Just in case of refactoring)
I am currently having a problem authenticating user in my SailsJS app using the PassportJS.
I have generated all the stuff needed for authentication using sails-generate-auth according to this tutorial.
It seems like the POST request is routed correctly as defined in the routes.js file:
'post /auth/local': 'AuthController.callback',
'post /auth/local/:action': 'AuthController.callback',
In AuthController I've got following code:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/user/show/' + user.id);
});
});
},
I am submitting my login form in Jade template:
form(role='form', action='/auth/local', method='post')
h2.form-signin-heading Please sign in
.form-group
label(for='username') Username
input.form-control(name='username', id='username', type='username', placeholder='Username', required='', autofocus='')
.form-group
label(for='password') Password
input.form-control(name='password', id='password', type='password', placeholder='Password', required='')
.form-group
button.btn.btn-lg.btn-primary.btn-block(type='submit') Sign in
input(type='hidden', name='_csrf', value='#{_csrf}')
I have checked the values passed to the callback function specified for password.callback(..) call:
passport.callback(req, res, function (err, user, challenges, statuses) {
the user variable is set to "false". I suppose, this is where the error comes from.
What is interesting is, that when the callback() function is called after user registration, the user variable is set right to user object containing all the values like username, email, etc.
If you would like to check other source files, my project is available on github in this repository.
Any help is appreciated.
Shimon
You're using identifier as the usernameField for LocalStrategy (i.e. the default setting) and have username in the login view, which means the authentication framework receives no username and fires a Missing Credentials error.
Either change the field name in the login view to identifier or set the appropriate usernameField through the passport config file (config/passport.js):
local: {
strategy: require('passport-local').Strategy,
options: {
usernameField: 'username'
}
}
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.