1

I'm using Node.js, MongoDB, and PassportJS and I'm trying to authenticate using the local strategy (just simple id and password). The problem is that everything in Mongo is asynchronous, but Passport is synchronous. I thought about trying to "hook" passport so that I could create my own callback to continue with authentication once the Mongo callbacks returned, but I don't know how the passport code works (and I'm not yet desperate enough to fire up a debugger and trace it).

The authentication process will actually finish successfully, but not before the app has responded to the client, causing the client to think it is still un-authenticated. For example, after the client tries to authenticate, it is redirected back to the login form because the session isn't authenticated. If I then simply refresh the page, I get in because, by that point, the authentication callbacks have returned.

passport.use(new localAuth(function(username, password, done)
{
    process.nextTick(function()
    {
        mc.connect('mongodb://127.0.0.1:27017/example', function(err, db)
        {
            if(err)
                throw err;

            db.collection('users').findOne({GUID: username}, function(err, results)
            {
                if(err)
                    throw err;
                console.log('Here I am!');
                db.close();
                return done(null, username);
            });
        });
    });
}));

(I realize the above code is not authenticating anything. I'm just trying to work through the asynchronous approach.)

I've tried multiple different variations on the above, but they all suffer from the same issue: the function given to localAuth() returns and passport proceeds with authentication before my database lookup has a chance to complete. I've seen several questions on SO about trying to force mongo to work synchronously and the responses are all "don't". I saw this question, which is exactly my scenario and my approach is the same, but I still have the issue of needing to refresh after the callbacks return.

How should I perform authentication in a node.js + mongoDB + passport app?

UPDATE I have noticed that 'Here I am!' appears in the log during the first attempt (ie. prior to refreshing the page). That makes me think that authentication is finishing and the session is set during the first authentication attempt. Just whatever is being returned from localAuth()'s callback (done()) isn't making it in time. Then when I refresh, passport tries to get the session and sees that it's there. This brings me back to trying to hook passport. Does anyone know when new localAuth() gets called during authentication?

UPDATE

function authenticateUser(id, pass, cb)
{
console.log('1');
    mc.connect('mongodb://127.0.0.1:27017/sp2010sec', function(err, db)
    {
        if(err)
            throw err;

console.log('2');
        db.collection('users').findOne({GUID: id}, function(err, results)
        {
            if(err)
                throw err;
console.log('3');

            return cb(err, id);
        });
console.log('4');
    });
console.log('5');
}

passport.use(new localAuth(function(username, password, done)
{
console.log('6');
    authenticateUser(username, password, function(err, user)
    {
console.log('7');
        return done(err, username);
    });
console.log('8');
}));

The above code produces this log:

6

1

5

8

2

4

3

7

Passport does not appear to be waiting for done to be called.

4

2 回答 2

2

I've gotten a band-aid over the asynchronous authentication, so I'll explain what I did. If anyone can give a better answer, I'd love to hear it.

So, the problem is that mongoDB is asynchronous, but passport is not and everything I could think of resulted in passport authenticating the request (or failing to, rather) before the mongo callbacks had returned. I tried multiple approaches to hooking passport so that I didn't even start the passport authentication until I had the mongo stuff done (sort of pre-authentication), but that didn't work either. I'm assuming that has to do with the internals of passport.

What I ended up doing was opening the db connection at app startup and leaving it open for the life of the app. This cuts down on one callback and I think this is letting the mongo callbacks finish in time for passport to see it. This feels like a potential race condition. I would love to see a better answer than this or an explanation as to what's really happening.

UPDATE

The real problem was that I wasn't using passport's success/failure redirects; I was handling that on the client side, which started as soon as the AJAX call returned (but was not involved in the passport callback chain). Once I started handling the redirects on the server side, the authentication started working as expected/advertised.

于 2013-06-28T17:01:19.940 回答
0

I don't think you want to have your user lookup wrapped in a process.nextTick() call. According to this answer, you only want to use nextTick() when you want that code to be called on the next iteration of the event loop. Simply removing that should fix the issue. This is similar to the code posted on the guide for passport.js.

Hope this helps!

于 2013-06-27T07:26:27.500 回答