passport icon indicating copy to clipboard operation
passport copied to clipboard

Add support promises flow

Open alexpts opened this issue 7 years ago • 38 comments

Return promise from all api method for work with passport via await/yield/then/(without callback) flow.

It will be nice! All new modules uses async/await now via babel or nodejs 7. It really more simple and clean.

alexpts avatar Jan 08 '17 07:01 alexpts

Since #225 and #243 have been created, it seems that a lot of people have been using promises and async/await for simpler control flow.

It's possible to promisify req.logIn in at least an Express app, but not obvious how to promisify passport.authenticate. It would be very helpful if promise support was added.

nhooey avatar Feb 26 '17 19:02 nhooey

@jaredhanson What are your thoughts on introducing Promise support now that using Promises and async/await is more common?

nhooey avatar Mar 08 '17 21:03 nhooey

👍

mazing avatar Mar 24 '17 10:03 mazing

@jaredhanson If I could reply to your sentiment about promises mentioned in your Stack Overflow answer from 2012:

Idiomatic Node.js

everyauth makes extensive use of promises, instead of Node's approach of using callbacks and closures. Promises are an alternative approach to async programming. While useful in some high-level situations, I wasn't comfortable with an authentication library forcing this choice upon my application.

Furthermore, I find that proper use of callbacks and closures yields concise, well architected (almost functional style) code. Much of the power of Node itself comes from this fact, and Passport follows suit.

I'd say that given the new async and await features of JavaScript, mentioned in the EMCAScript 2017 Draft (ECMA-262), and supported in stable Node.js releases with the --harmony-async-await command-line parameter, there is no reason to believe that callbacks yield "concise, well architected code" in relative terms. I believe the danger of creating "callback hell", where it's almost impossible to follow the control flow of the code, is the primary concern when writing well-architected code. Also, using Promises with async/await also doesn't prevent one from writing functional code.

Any decent, modern programming language with parallelism built in, and even old ones with parallelism tacked on, use Promises or Futures. Wikipedia has a list of languages that implement Promises, which includes the following languages with native Promise support: C++, Dart, Java, JavaScript, C#, Python, R, Scala, Swift, and Visual Basic, and more.

If you don't want to force Promises on your users, it would make sense to make a new major version of the Passport API while still fixing bugs in the old version.

Using a real concurrency construct like Promises should be the way forward for any Node.js library.

nhooey avatar Mar 24 '17 11:03 nhooey

Just to note, Promises and callbacks aren't necessarily exclusive, you can support both in one function:

const asyncFunc = (callback) => {
  const doAsyncThing = (cb) => {/* Do something asynchronous here, then call cb() */};

  if (!callback) {
    return new Promise(resolve => doAsyncThing(resolve));
  } else {
    doAsyncThing(callback);
  }
}

Now both of these work, which means this would only be a feature release rather than a major release since it doesn't break anybody!

asyncFunc(() => { /* Callback when finished */ });

asyncFunc()
  .then(() => { /* Resolve promise when finished */ });

mxstbr avatar Apr 11 '17 15:04 mxstbr

@jaredhanson Do you have any thoughts on adding promise support to Passport?

nhooey avatar Apr 16 '17 20:04 nhooey

Any update on this? It would make everything so much cleaner!

alexandernanberg avatar Jun 22 '17 20:06 alexandernanberg

Has any one tried to use the new promisify function builtin into the util module from node v 8.0 and up?

walleXD avatar Sep 14 '17 18:09 walleXD

I tried using the promisify function to promisify things in Passport a few months ago. Although I can't remember much about what I did, I remember stopping at some point because I would have to change a lot of code within Passport and libraries it depended on. The library needs an overhaul, but @jaredhanson hasn't replied to a single message about this for almost 7 months now.

Someone should just fork the project and support promises.

nhooey avatar Sep 14 '17 18:09 nhooey

What benefits would promises offer an application built on Express, using the middleware pattern?

For example, passport is used via middleware like:

app.post('/login', passport.authenticate('local'));

Why does that need to return a promise?

jaredhanson avatar Sep 14 '17 19:09 jaredhanson

I am using express with graphql. So, I am using passport to secure my graphql endpoint but the issue is that graphql only works with promises and so it becomes necessary to wrap functions like passport.authenticate with another function which returns a promise

Also, most of the JS community (browser & node) is definitely moving fast towards adoption of async/await to handle async actions with promises over callback. So, adding support for promises would make passport easier to use with newer libraries which are coming out now and also in the future as they tend to mostly use promises (graphql being an example from the last few years)

walleXD avatar Sep 14 '17 20:09 walleXD

@jaredhanson Also, re: middleware, Express 5 is going to have explicit promises support.

mikemaccana avatar Sep 18 '17 09:09 mikemaccana

Someone needs to PR with an alternate interface

zenflow avatar Sep 27 '17 03:09 zenflow

@mikemaccana Where did you read that? https://expressjs.com/en/guide/migrating-5.html no mention of promises... ?

zenflow avatar Sep 27 '17 03:09 zenflow

@zenflow Yeah, because they are already supported (somehow) in 4.x: https://expressjs.com/en/advanced/best-practice-performance.html#use-promises

michaelwalloschke avatar Sep 27 '17 06:09 michaelwalloschke

+1 for native promise support

mayeaux avatar Oct 02 '17 02:10 mayeaux

@michaelwalloschke Hmm, using promises is definitely advocated, but it doesn't look like there's built-in support...

Note that the second example under your link uses a wrap function on the request handler:

app.get('/', wrap(async (req, res, next) => {
   ...

mattbrunetti avatar Oct 02 '17 12:10 mattbrunetti

This looks like the wrap function used: express-async-wrap

mattbrunetti avatar Oct 02 '17 12:10 mattbrunetti

@jaredhanson Mongoose and Sequelize prefer promises over callbacks now, so in a passport-local strategy, the common pattern will become

// strategy.js
...
User.findOne({ where: { email })
  .then((existingUser) => { throw new myUserExistsError(`user with email ${email} already exists`; })
  .then(() => User.create(userData))
  .then(newUser => done(null, newUser, { message: 'success' })
  .catch((error) => {
    if (error instanceof myUserExistsError || error instanceof Sequelize.UniqueConstraintError) {
      return done(error, false, { message: 'user already exists' } );
    }
    return done(error, false, { message: 'some other error' } );
  });
...

// handler.js
(req, res, next) => {
  return passport.authenticate('local-signup', (error, user, info) => {
    if (error) return res.status(400).json({ message: info });
    if (user) return res.status(200).json({ message: info });
    return res.status(401).json({ message: info });
  }
})(req, res, next);

where passport.authenticate can only accept a callback, making this a done hell when the strategy definition uses a few more thens and dones. Besides this forces many new to passport/promises into using an anti-pattern of mixing promises with callbacks, on top of ensuring done is called once and only once. Let's not assume people know how to convert between the two, especially people who follow the tutorials.

zhanwenchen avatar Nov 05 '17 21:11 zhanwenchen

lol I think done() is dead. Promises have gotten popular due to await needing them, not because we like endless method chaining!

mikemaccana avatar Nov 07 '17 12:11 mikemaccana

@zenflow See https://github.com/expressjs/express/pull/2237, the Express 5 release tracking bug list. Top item:

Add support for Promises in all handlers #2259

if you follow it, you'll end up at Router 2.0 which is https://github.com/pillarjs/router/pull/60

mikemaccana avatar Nov 07 '17 12:11 mikemaccana

Await is way better than endless chaining and has been supported for a long time, it has my vote

mayeaux avatar Nov 07 '17 19:11 mayeaux

Agreed. async/await has become the new de-facto standard, respected by many libraries such as Mongoose and Express.js

It would be only natural for Passport.js to support it as well, also given the growing demand

kerberjg avatar Nov 15 '17 12:11 kerberjg

As of today passport's done() is the only oldschool error handling artifact in my app. It seems that everyone else moved to promises. You asked about benefits @jaredhanson and I think one obvious benefit is productivity and that if you teach a junior like myself it's easier to teach one concept of error handling, not a few different ones. Also mixing callbacks and promises makes testing tricky. Like others mentioned here this doesn't have to be a breaking change because promises and callbacks don't have to be exclusive- check out bcryptjs and mongoose for example. Thoughts?

jakubrpawlowski avatar Nov 29 '17 22:11 jakubrpawlowski

Are you referring to done as needed in verify callbacks?

jaredhanson avatar Nov 29 '17 23:11 jaredhanson

I'm referring to LocalStrategy example where we pass errors to done(). In my app I have calls to database and async password hashing inside that callback which both return promises so if I only knew a way to make passport return a promise too I could get rid of done() and make it all look uniform. Does this make sense?

jakubrpawlowski avatar Nov 29 '17 23:11 jakubrpawlowski

I thought about it all day- I've read entire documentation over and over again and it finally makes sense to me why you went with done() to cover all the possible outcomes as described in Verify Callback. As much as I wish there was a simpler way to do this I'm afraid that I can't come up with a more elegant solution that would work in Express 4.x. It looks like one may be possible in Express 5.x. Sorry for wasting your time!

jakubrpawlowski avatar Nov 30 '17 07:11 jakubrpawlowski

@jaredhanson is this issue on your radar?

Misiur avatar May 05 '18 19:05 Misiur

What benefits would promises offer an application built on Express, using the middleware pattern?

I think one of the best arguments is how this can simply building unit tests. This article by Gustaf Holm shows how promises and async / await simply test code, and make it easier to write code that is easy to test.

torenware avatar Sep 21 '18 21:09 torenware

Jared works at Auth0 now, and hasn't made any meaningful commits to this repo in forever. He does auth programming all day—for money. Why do you guys expect him to swoop in and save the day?

Passport doesn't seem like it's doing a ton of stuff. Maybe it's time for another hero to rise up and make some auth library from scratch?

Then again, it seems everyone could just use https://nodejs.org/api/util.html#util_util_promisify_original where needed.

corysimmons avatar Dec 12 '18 08:12 corysimmons