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'
}
}
Related
I am using the library auth0/nextjs. I am trying to handle the email verification. I have access to the email_verification variable. If not verified, it will redirect to the page /please-verifiy-your-email.
At the moment I am using handleCallback method which is provided by auth0/nextjs.
Code:
const afterCallback = (req, res, session, state) => {
if (!session.user.email_verified) {
res.status(200).redirect('/please-verifiy-your-email')
}
return session;
};
export default auth0.handleAuth({
async login(req, res) {
try {
await auth0.handleLogin(req, res, {
authorizationParams: {
audience: 'https://dev-okz2bacx.us.auth0.com/api/v2/',
scope: 'openid profile email read:branding'
},
returnTo: "/dashboard"
});
} catch (error) {
res.status(error.status || 400).end(error.message);
}
},
async callback(req, res) {
try {
await auth0.handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
}
});
How can I make sure, the user is still logged in, to get his email for a resend or the possibility to change his email.
He also needs the chance to do a new sign up , because when I will call api/auth/login after login with the unverified email, it will redirect automatically to /please-verifiy-your-email. I guess the session is not killed and auth0 goes back redirecting even tho I didn’t had the chance to sign up again.
Would be really cool if I get a few inputs.
It would solve the problem if I could log in the user and then it would redirect to /please-verifiy-your-email. I would be able to get his email address, name for further functions like resend verification. Right now I call api/auth/me I get:
{"error":"not_authenticated","description":"The user does not have an
active session or is not authenticated"}
I'm using passport.js with mongoose (mongoDB) (and passport-local-mongoose) to create and authenticate user accounts in a node/express backend.
The strategy I'm currently using is:
User completes a signup form, with a username input type="email" name="username" and a password input name='password' (among others)*
User submits form.
Add new user to a Profile schema let newUser = new Profile({username: req.body.username, ...})
Generate a token using crypto.randomBytes(...)
Profile.register(newUser, password) & passport.authenticate user and set token field generated from above set, along with an expiry date
Send an email to new user, with a url containing their mongoose _id and their token (/u/${id}/is-valid/${token}).
On visiting this url, Profile.findOne({_id: req.params.id}) and check that the tokens match, and has not expired
If not, send them to the login page.
Then problem with this approach, is that in calling Profile.register and passport.authenticate together, a pre authenticated account is created before the user has validated their email.
How can I decouple these two methods so that I can authenticate users only after they've verified their email address?
Here is how I call Profile.register and passport.authenticate (as part of an async.waterfall([]):
Profile.register(user, pw)
.then(response => {
passport.authenticate("local")(req, res, function(err) {
if (err) return callback(err);
user.resetEmailToken = token;
user.resetEmailExpires = Date.now() + 8.64e+7; // 1 day
user.save(function(err) {
callback(err, token, user);
});
});
})
.catch(err => {
return callback(err);
});
I would like to Profile.register as I do above, but only passport.authenticate once I've checked the tokens.
Here is how I check the tokens (on visiting link contained in email - /u/${id}/is-valid/${token}):
async verifyEmail(req, res, next){
let user = await Profile.findOne({ '_id': req.params.id })
if(!user) return res.redirect("back");
if (user.resetEmailToken != req.params.token){
req.flash("error", "Tokens don't match");
return res.redirect("back");
}
if(user.resetEmailExpires < Date.now()){
console.log("This date has expired", moment(user.resetEmailExpires).calendar());
return res.redirect(`/signup?token-expired=${user.email}`);
}
user.isVerified = true;
// Here is where I want to passport.authenticate() and redirect to their new profile
user.save();
return res.render('login', { user: user });
}
If this is not possible, what are other approaches I could take to achieve this? It must be a fairly common signup strategy.
* This is a bit of a hack as passport.js assumes registration with username & password (not email)
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 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 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.