Why is my Passport auth function not executing when called within a route?

Viewed 918

I'm using Passport to protect the front and back end of a MEAN stack app. The app is structured like so:

monstermash
    config // server configuration
    public // static directory that will serve the entire Angular frontend
    app
        index.js // initialization of the server
        models 
            index.js // mongoose schemas and models
        passport 
            index.js // configuration for passport and all my strategies
        routes 
            index.js // basic route definitions for the API (using functions defined under v1, below) and UI (routes defined inline here for simplicity's sake)
                v1
                    index.js // all the functions called to power the API routes

Here's app/index.js since I know it's sometimes a matter of calling application middleware in the right order:

var express = require('express');
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var mongoose = require('mongoose');
var app = express();
var CONFIG = require('config').BASE;
var bcrypt = require('bcrypt-nodejs');
var passport = require('passport');
var flash = require('connect-flash');
var models = require('./models');

app.passport = require('./passport');
app.port = CONFIG.PORT;
app.use(bodyParser.json()); 
app.use(bodyParser.urlencoded({
    extended: false
})); 
app.use(allowCrossDomain);
app.use(express.static('public'));
app.use(cookieParser());
app.use(session({ 
    secret: 'keyboard cat',
    resave: true,
    saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

var routes = require('./routes');

app.use(express.static('public', {redirect:false}));
routes(app)

module.exports = app

passport/index.js looks like this. A lot of the commented-out bits are only cut out in an effort to get this down to bare bones for debugging:

var models = require('../models')

passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy
  , LocalAPIKeyStrategy = require('passport-localapikey-update').Strategy;

passport.use('localapikey', new LocalAPIKeyStrategy(
  {apiKeyHeader:'x-auth-token'},
  function(apikey, done) {
  console.log('api key');
    models.User.findOne({ apikey: apikey }, function (err, user) {
      if (err) { return done(err); }
      if (!user) { return done(null, false); }
      return done(null, user);
    });
  }
));

passport.use('local-signup', new LocalStrategy(
  function (req, username, password, done) {
    console.log('trying local');
    models.User.findOne({
      local: {username: username}, function (err, user) {
        if (err) {
          return done(err);
        }
        if (!user) {
          console.log('no user');
          return done (null, false);
        }

        if (!user.validPassword(password)) {
          console.log('bad pwd');
          return done(null, false);
        }
        return done (null, user);
      }
    })
  }
));

module.exports = passport;

The localaipkey strategy is included here just to illustrate that it works and is configured in much the same way as the local-signup one.

Then my routes/index.js looks like this. HTML for login forms is inline here because this is just a perliminary test. Note that I'm not doing anything other than checking validation. Including one of the API routes here too do demonstrate how that's set up. The UI code here is lifted straight from a Passport tutorial, since I went back to the drawing board and got rid of my own code on the matter.

var v1 = require('./v1');

  // API routes as an example. This authentication is called before the route and works fine.

  module.exports = function(app) {
    /* API: V1 */
    app.route('/v1/monster/:id')
      .put(
        app.passport.authenticate('localapikey', { session: false }),
        v1.monster.update)
      .delete(
        app.passport.authenticate('localapikey', { session: false }),
        v1.monster.delete
      );

    // My test login routes. Here, authenticate is called inside the route because it's the handler for logging in.

    app.route('/login')
      .post(
        function (req, res) {
          console.log(req.body);
          app.passport.authenticate('local-signup', { 
            successRedirect: '/root',
            failureRedirect: '/fail'
          });
      })
      .get(function (req,res) {
        res.send('<!-- views/login.ejs -->\
          <!doctype html>\
          <html>\
          <head>\
              <title>Node Authentication</title>\
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->\
              <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->\
              <style>\
                  body        { padding-top:80px; }\
              </style>\
          </head>\
          <body>\
          <div class="container">\
          \
          <form action="/login" method="post">\
            <div>\
                <label>Username:</label>\
                <input type="text" name="username"/>\
            </div>\
            <div>\
                <label>Password:</label>\
                <input type="password" name="password"/>\
            </div>\
            <div>\
                <input type="submit" value="Log In"/>\
            </div>\
        </form>\
          \
          </div>\
          </body>\
          </html>');
      });

So that form submits a POST:/login request with the form data. The form body is there in req.body but the console.log messages I have in the validation function never get logged. The form submission just hangs and hangs; there's no res.send() on that route because the authentication should either pass or fail and never get to that, but the whole app.passport.authenticate() function is just totally bypassed.

I've done a lot of trial-and-error on this, and I've found that if I call app.passport.authenticate() with the name of a strategy that isn't even registered, the same thing happens: there's no failure message, it just continues on with the route like it wasn't even there. So maybe the issue is that this is happening and it's not recognizing the local-signup strategy being registered, though I don't know why that would be and the localapikey strategy is found.

Side note, I actually am testing this with a username and password set in the form; I found one SO question on that from someone who was trying empty or passwordless submissions and not seeing their validation function execute, so I'm sure it's not that.

1 Answers
Related