rkusa/koa-passport

koa 2 not handling unsuccessful login

dagda1 opened this issue · 16 comments

I am confused as to what is the correct way to use koa-passport with koa 2.

So far I have my strategy defined like this:

import Strategy from 'passport-local';

passport.use('local', new Strategy({
  usernameField: 'username',
  passwordField: 'password'
}, async (username, password, done) => {
  try {
    const user = await User.findOne({
      username
    });

    if (!user) {return done(null, false)}

    try {
      const isMatch = await user.validatePassword(password);

      if (!isMatch) {
        return done(null, false)
      }

      done(null, user)
    } catch (err) {
      done(err)
    }

  } catch (err) {
    return done(err)
  }
}))

And I have my auth route defined like this:

const router = new Router();

router.post('/', function(ctx, next) {
  return passport.authenticate('local', function(err, user, info, status) {
    console.dir(arguments.length);
    if(user === false) {
      console.log('it gets here but then........');
      return ctx.throw(401);
    }

    const token = jwt.sign({ id: user.id }, config.token);

    const response = user.toJSON()

    delete response.password;
    delete response.salt;

    ctx.body = {token,user: response};
    ctx.status = 201;
  })(ctx, next)
})
export default router;

This appears to work fine for the successful login but appears to hang and then comes back with this response

Empty reply from server
rkusa commented

Where exactly do you get the error Empty reply from server?

With the following curl request:

curl -H "Content-Type: application/json" -X POST -d '{"username":"user","password":"fffff"}' http://localhost:5000/auth/

The code seems to hang at the return ctx.throw(401); line.

Is there an example somewhere of how to properly return a 401?

rkusa commented

Could you append -i to this curl command and put the output here?

If I make this request:

curl -i -H "Content-Type: application/json" -X POST -d '{"username":"dagda1","password":"5a5parellazzzz"}' http://localhost:5000/auth/

After a wait of at least a minute, I get the following response:

curl: (52) Empty reply from server

rkusa commented

I am not yet able to reproduce this issue. So we have to isolate it a little bit.

Could you replace your whole return passport.authenticate(...) function by just return ctx.throw(401);. Do you get a proper 401 response in this case?

yes I do

It does appear to be the combination of the ctx.throw in the passport block. If I log in successfully, I get this response:

HTTP/1.1 201 Created
Vary: Accept-Encoding
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: application/json; charset=utf-8
Content-Length: 263
Date: Thu, 27 Oct 2016 21:16:25 GMT
Connection: keep-alive

rkusa commented

So return ctx.throw(401); outside of passport.authenticate does also work?

Yes, outside of the block returns this response:

HTTP/1.1 401 Unauthorized
Vary: Accept-Encoding
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Date: Thu, 27 Oct 2016 21:20:13 GMT
Connection: keep-alive

I have found the problem, I had not marked the function as async.

If I change the code to this, it works fine:

router.post('/', function(ctx, next) {
  return passport.authenticate('local', async function(err, user, info, status) {
    if(user === false) {
      return ctx.throw(401);
    }

Apologies for wasting your time.

rkusa commented

Don't worry, such issues always provide a possibility to learn. For example now, I do not really understand why adding the async there solves this issue. I also found out something else. The try/catch in the LocalStrategy is related to the issue. Because the ctx.throw throws an exception, which will be catched by this try/catch, which just calls done again.

there must be some await call or generator call somewhere that was just being suspended without the async marker.

rkusa commented

I think I have an idea why this happens. I you provide an async function to passport.authenticate, the error is catched by koa-passport and handled accordingly. Otherwise, the error is handled by your try/catch. So your solution for the issue is perfectly fine. However, I have to think about how to improve the usage of strategies with async/await. Having to use a try/catch does not seem like a nice solution ...

rkusa commented

With version 3.0.0-rc.2 you should now be able to remove the try/catch inside of your new LocalStrategy

If the try catch is removed then where is the Promise rejection handled?

rkusa commented

The error is now properly forwarded to Koa (move the try catch here 53b412c)