1

I am trying to use passport to authenticate users on the web page. Everything works fine, except when authentication fails and the passport redirects the user to the same rout, all the data on the form is lost. Is there a way to persist the data and pass them back to the form.

I have the following in routes.js

// =====================================
// SIGNUP ==============================
// =====================================
// show the signup form
app.get('/signup', function(req, res) {
    // render the page and pass in any flash data if it exists
    signup.isAuthenticated = req.isAuthenticated();
    signup.user = req.user;
    signup.message = req.flash('signupMessage');
    res.render('signup', signup);
});

// process the signup form
app.post('/signup', passport.authenticate('local-signup', {
    successRedirect : '/', // redirect to the secure section
    failureRedirect : '/signup', // redirect back to the signup page if there is an error
    failureFlash : true // allow flash messages
})); 

on my passport.js I have the following:

// =========================================================================
// 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
        usernameField : 'email',
        passwordField : 'password',
        passReqToCallback : true // allows us to pass back the entire request to the callback
    },
    // this function is used when signing up
    function(req, email, password, done) {
        // TODO: get the user from data
        if(email == 'myemail@gmail.com') {
            // user email already exists    
            console.log('user already exists !');
            return done(null, false, req.flash('signupMessage', 'That email is already taken.'));
        } 
        else {

            // if there is no user with that email
            // create the user
            var newUser = { username : 'myemail@gmail.com', name : 'Name Surname' };
            newUser.local.email    = email;
            newUser.local.password = newUser.generateHash(password);
            return done(null, newUser);
        }
    }));   

and my server.js has the following:

// server.js

// set up ======================================================================
// get all the tools we need
var express  = require('express');
var path     = require('path');
var app      = express();
var port     = process.env.PORT || 3000;  
// var mongoose = require('mongoose');
var passport = require('passport');
var flash    = require('connect-flash');

var morgan       = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser   = require('body-parser');
var session      = require('express-session');
var multer       = require('multer');

var configDB = require('./config/database.js');

// configuration ===============================================================
// mongoose.connect(configDB.url); // connect to our database

require('./config/passport')(passport); // pass passport for configuration

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// set up our express application
app.use(morgan('dev')); // log every request to the console
app.use(bodyParser.json()); // get information from html forms
app.use(bodyParser.urlencoded({ extended: false }));  

// use multer to process multi-part requests and multer to save our files by default to /uploads/ directory
app.use(multer({ 
            dest : path.join(__dirname, '/uploads/'),
            limits : {
                fieldNameSize : 200,       // 200 bytes 
                files : 5,                 // 5 files
                fileSize : 5194304000000,  // 5 GB 
                fields : 50                // 50 fields on the form                 
            }
}))

app.use(cookieParser()); // read cookies (needed for auth)
app.use(express.static(path.join(__dirname, 'public')));

// required for passport
app.use(session({ 
    secret: 'mylongsecretpassphrase', 
    resave : true, 
    saveUninitialized : true 
})); // 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

// routes ======================================================================
require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport

// show error page if the resource is not found
app.use('*', function(req, res) {
  res.render('page-error', { 
    title : 'myWeb - Page Error',
    description : 'my web page',
    keywords : 'keywords1, keywords2, keywords3'
  });
});

// launch ======================================================================
app.listen(port);
console.log('Node listens on port ' + port);

any help would be greatly appreciated !

fitims
  • 323
  • 1
  • 4
  • 10
  • One way I've seen this done in express is by storing the data in a server-side session object, then when the redirected request comes in, you can check if saved data is in the session object and, if so, then you put it into the page as the page is served and then clear the data from the session. I think connect-flash is one such module for doing this. You could also put the data into a cookie, though the server-side session keeps you from having to send it back to the client and then have the client send it back to server all during the redirect. – jfriend00 Oct 11 '14 at 21:28
  • 1
    you could try sending the form through ajax, so you don't change page if the auth is unsuccessful – xShirase Oct 11 '14 at 21:51

2 Answers2

2

If you don't want to lose form data, you could use AJAX to send your form, and send a status 401 Unauthorized in case the auth fails. Passport sends the 401 by default so the following should work (untested, may contain typos) :

app.post('/login', function(req, res, next) {
    passport.authenticate('local-signup',
      function(req, res) {
       // If this function gets called, authentication was successful. If not, your ajax call gets a 401 status and you can handle it in .fail()
        res.redirect('/');
      });
});

A bit of explanation from the passport website :

By default, if authentication fails, Passport will respond with a 401 Unauthorized status, and any additional route handlers will not be invoked. If authentication succeeds, the next handler will be invoked and the req.user property will be set to the authenticated user.

xShirase
  • 11,975
  • 4
  • 53
  • 85
  • actually the code above didn't work for my requirement, but the AJAX worked perfectly. Thanks for the tip. – fitims Oct 29 '14 at 10:10
  • Take note that this won't work for OAuth2 applications (passport-facebook, passport-google-oauth) as they don't support ajax by design: https://stackoverflow.com/questions/43276462/cors-issue-while-making-an-ajax-request-for-oauth2-access-token/43276710#43276710 – clodal Jul 28 '18 at 04:49
2

Instead of using the default callbacks like

passport.authenticate('local-signup', {
    successRedirect : '/', // redirect to the secure section
    failureRedirect : '/signup', // redirect back to the signup page if there is an error
    failureFlash : true // allow flash messages
})

you can use a custom callback, and pass whatever input vars you want via flash messages like so

router.post('/signup', function(request, response, next) {
    passport.authenticate('local-signup', function(err, user, info) {
        if (err)
            return next(err);

        if (!user) {
            // Attach flash messages to keep user input
            request.flash('emailInput', request.body.email);
            request.flash('usernameInput', request.body.username);

            return response.redirect('/signup');
        }

        // Note that when using a custom callback, it becomes the application's responsibility 
        // to establish a session (by calling req.login()) and send a response.
        request.logIn(user, function(err) {
            if (err)
                return next(err);

            return response.redirect('/profile');
        });
    })(request, response, next);
});

then, when redirected, you can send the flash messages to your view templates like normal

response.render('signup.ejs', { 
    signupMessage: request.flash('signupMessage'),
    emailInput: request.flash('emailInput'),
    usernameInput: request.flash('usernameInput')
});
internet-nico
  • 1,637
  • 19
  • 17