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.

2 Answers 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.

Sign up to request clarification or add additional context in comments.

6 Comments

Passport supports asynchronous operation, that's why it passes the done callback function which you're supposed to call once your (async) user check is finished. There is a potential race condition with opening the database at app startup, but you can always wait for the database to connect before starting to accept incoming requests to your Express application.
The problem was that the callbacks weren't finishing in time--so I open the db connection early to remove one of the callbacks that must finish. But what if findOne takes longer than normal? I think I could end up in the same situation as before. In the code above, done is called in the last callback (as you suggest), but after much testing I have seen that passport does not wait for this. As soon as mc.connect finishes, passport moves on. It does finish, which is why the second request (when I refresh the page) gets authenticated. I've added an additional example to the question
I think you're missing the finer nuances of asynchronous execution: when an async function is called, execution of the code following that call will continue. Passport won't (or rather: can't) wait for an async function to finish before continuing, therefore it passes a callback which will be called when the async function is done. In your example: you can't expect #8 to be logged last, because any of the async functions can finish later.
Yes, you are correct about asynchronous execution. So, how do I authenticate when the verification function returns before the actual verification is complete? This is what's happening: [me]: Can I come in? [you]: Who are you? [me]: (give you ID) [you]: (async checks ID, but before complete) No, I don't know you. [me]: (wait while async completes) Are you sure? [you]: Oh, yes, I know you. Please come in.
Perhaps the question is now larger than passport. If passport "will get back to me" whenever it feels like it, then what should the server tell the client? Should the app somehow delay responding until it detects that passport's done? (that can't be correct, can it?) What's the standard approach to this?
|
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!

1 Comment

I've tried both with and without the nextTick(). There are so many callbacks involved that the issue still occurs in either variant.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.