-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Return a Promise #111
Comments
+1 |
Everything that occurs during signing/verifying/decoding is completely synchronous. What benefit would a promise provide? |
Verify currently offers a synchronous operation or an optional callback (which simply defers evaluation to a future turn of the event loop). It's unclear to me why the callback version exists. I also think it's an antipattern to overload the same method to behave both synchronously and asynchronously depending on the arguments. |
What I wanted to say is that |
Sure, though as @jden pointed out the callback version of |
Well... as I can see the sync version of @omsmith I agree that the callback is useless and I would be more pleased if the sync version would just return |
As already pointed by @xpepermint, using promises would be more consistent with the rest of the code. |
Could any of the maintainers comment about whether the intention is to refactor the implementation of I'm still +1 to separating async/sync into separate functions (as node core does, e.g. in |
While streaming would be possible, there is no async sign/verify in crypto |
@omsmith good point, and at the size that JWTs are, streaming is unnecessary overhead. My vote would be to remove the async function signature then. |
Hey everyone, There are different things here.
CPU intensive async calls (crypto) make use of libuv's event loop to simulate asynchrony (they are of course working in another thread). We don't have the option to simulate that here. The best choice would probably be to get rid of the callbacks. As of now it makes no sense to do so. Once we update to another major we can probably make this change. |
I'd like to point out that there's a difference between async and not throwing. The Node pattern of using a callback like So while I'm -1 on returning a promise, I'm also -1 on throwing. |
That's not really true, though - the Promises/A+ spec exists exactly to make this not the case. You can simply return the promise from a handler provided by whichever promises library you're using, and then handle it as normal in your implementation of choice. The only way it would be "tied to" an implementation, is in that it is a dependency. The argument that "it can be promisified" normally misses half the point of promises - one of the things that promises give you is reliable error handling, by making it hard to 'forget' to handle or propagate an error. Using promises internally in a library therefore greatly increases the reliability of said library, compared to using nodebacks (ie. the async All that being said, if the library isn't operating asynchronously to begin with, there's no point to having either a callback or a promise. I'd even say that it's misleading, as it isn't actually asynchronous. Throwing in a fully synchronous method is perfectly okay, by the way - promise libraries will automatically convert this into a rejection, and when not using promises, they can be handled using a regular |
Chiming in here -- the most compelling reason to return Promises is for interoperability with async functions. They are the lingua franca of asynchrony in JS. try {
let payload = await jwt.verify(token, secret, { issuer: 'https://example.com' });
} catch (e) {
...
} But if edit: the |
👍 |
this. |
I had to go digging through the source code and issue tracker in order to make sure there were no blocking operations that make the non-callback version unsuitable for production use. |
tl;dr Promises are good, but jsonwebtoken doesn't need them. So I previously commented that I'm -1 on returning a Promise, but I've come around on promises as the ideal method of flow control for things that have potential error states. Compare the following code: // try/catch
(function () {
let result;
try {
result = jwt.verify('....token', secret);
} catch (e) {
if (err instance of jwt.JsonWebTokenError) {
// ...something
} else if (err instance of jwt.NotBeforeError) {
// ...something else
} else if (err instance of jwt.TokenExpiredError) {
// ... a third thing
} else {
// assume the Error interface
}
}
// result should be ok
}());
// via nodebacks
jwt.verify('....token', secret, function (err, result) {
if (err instance of jwt.JsonWebTokenError) {
// ...something
} else if (err instance of jwt.NotBeforeError) {
// ...something else
} else if (err instance of jwt.TokenExpiredError) {
// ... a third thing
} else if (err instance of Error) {
// assume the Error interface
} else if (err) {
// can this even happen?
} else {
// result should be ok
}
});
// via promises
Bluebird.try(() => jwt.verify('....token', secret)).
then(result => { /* result should be ok */ }).
catch(jwt.JsonWebTokenError, err => { /* something */ }).
catch(jwt.NotBeforeError, err => { /*something else */ }).
catch(jwt.TokenExpiredError, err => { /* a third thing */ }).
catch(err => { /* definitely an error */ }); Personally, I prefer the Promise interface. Also, the standard try/catch interface is an optimization killer. The good news is that promisifying jsonwebtoken's interface is so easy that it feels out of scope for this library: const jwt = require('jsonwebtoken');
const promisify = require('bluebird').promisify;
exports.JsonWebTokenError = jwt.JsonWebTokenError;
exports.NotBeforeError = jwt.NotBeforeError;
exports.TokenExpiredError = jwt.TokenExpiredError;
exports.decode = promisify(jwt.decode);
exports.sign = promisify(jwt.sign);
exports.verify = promisfy(jwt.verify); |
@ry7n To address your points:
|
@joepie91 I don't think we're disagreeing; neither of us think JSONWebToken should return a promise. |
@ry7n Right, but it seems to be for a different reason. Hence attempting to clarify, since the callback API should probably be deprecated as well :) |
+1, any progress? |
This issue should be renamed to "deprecate callback API" |
or "remove async API and bump major version" |
Could this please be clarified in the As |
@felixfbecker, good to see you here :-) I totally agree with @calebmer that this should be made clear in the README. As a new |
Ended up doing |
@marcushultman, while that works, a Promise is just as moot as a callback. |
right, but the point is this isn't really async, so it shouldn't need to be wrapped in anything. |
Yeah I didn't really read far enough through the thread, it's synchronous whatever way so it's all a bit pointless, gotcha. |
|
This comment has been minimized.
This comment has been minimized.
Weighing in on this, would be nice if we could actually have a version which is implemented with promises internally. I was trying to use Google Cloud KMS instead of the node provided crypto libraries to handle signing (and verification), without the need to write all the JWT stuff myself. Given these interactions are inherently async, this seems currently not possible. |
I don't get why this is such a big deal? Why do Promises is the standard way for doing async stuff nodejs nowadays, if you don't want to break the current sync API that it returns an unnecessary promise, at least a I don't think people use bluebird anymore nor want to install an external library just for that now that ES6 Promises are part of the language.. callbacks are seen as an anti-pattern and its weird having them when the rest of the consuming codebase is using Promises.. wrapping the function with a Promise or using |
What is the point of exposing either callback or Promise anyway i ask? Node's The only thing, that i'd see as a quirk rather than a feature is Bottom line, callbacks should have never been included in the first place and exposing Promise now is just wasted effort. If you came here asking for Promise just because you don't like callbacks - use the sync interface instead, if you need promise because you don't like try/catch - wrap it. @ziluvatar how about we just remove the mention of callbacks from the docs. edit: Node's crypto module now supports true non-blocking and async sign/verify which we could use should we want to revisit this topic. https://github.com/panva/jose makes use of those APIs. |
+1 for removing the callback API altogether and bump major version OR* support Promises, even though they're just useless in this case, but at least consistent with A) current "async" callback API (quotation marks important) B) current way of writing and error handling in asynchronous Node code: try {
let foo = await bar();
let payload = await jwt.verify(token, secret, { issuer: 'https://example.com' });
} catch (e) {
...
} The current way is just neither here nor there and it's very confusing. Proof: this issue discussion. And requiring a bluebird or promisify nowadays is just... not right. * XOR |
@jiri-vyc That code will execute and work exactly as expected if you just remove +1 to remove the callback API which serves to confuse people into thinking that the methods of this library are async. I say wrapping any method here in a promise is not a great idea, it's just a waste of time just to conform to a pattern that is phasing out (with async/await phasing in). Sync interface is the only way to go here unless things become actually async, which I can't really see happening after reading this discussion. |
If you're using an async I didn't see a good example, so here's what I ended up with. pseudocode-only import { verify as jwtVerify, decode as jwtDecode } from 'jsonwebtoken';
import * as JwksClient from 'jwks-rsa';
import { promisify } from 'util';
const jwkClient = JwksClient();
const getKey = async(header) => {
const getPubKey = promisify(jwkClient.getSigningKey);
const key = await getPubKey(header.kid);
const pubKey = key.getPublicKey();
return pubKey;
}
const tokenHeader = jwtDecode(token, {complete: true}).header;
const pubKey = await getKey(tokenHeader);
const decoded = jwtVerify(token, pubKey); |
I was confused by this as well. I almost blindly just promisified the callback thinking "if there's a callback version, there must be some benefit such as offloading the signing/verification to another thread". |
Using promisify is not a solution. To the point that callbacks are pointless, they are not. Verify accepts an asynchronous (not async) function, apparently for convenient usage of JWKS. As noted above the key can be decoded once to extract the Being hosted by Auth0, it is perplexing that this should be an awkward experience, they basically require JWKS. |
@Sever1an - To clarify, my solution isn't using Agreed, the callback function signature of
|
@AcBerk - My deception was from a place of ignorance. How did I never know? Thank you for the free node.js lesson. 👍 The solution of wrapping |
I personally would not remove the "asynchronous" version of The RFC is not completely implemented, which is incidentally how I came across this issue. I'm looking to perform X.509 certificate chain validation, which unfortunately is not supported by this library yet. I plan to use the "x5c" header, which likely would not cause issues if That said, even if the decision was made to make certificate validation a separate process, I would expect it to be asynchronous (at least for "x5u"), and I would expect it to support promises natively. Sure, you can force everyone to manually wrap everything they plan to use, but why? You could manually wrap the |
It looks to me like jose is a good alternative here and is already quite popular.
|
Given verify return decoded data, There seems to be overhead to decode same token twice. |
Whats the state of this? The docs still say Because that is what I assumed until I saw this issue here. |
More documentation here is needed. Thanks @AcBerk for providing the example above |
I'd argue we should probably see functions clearly broken down into their asynchronous and synchronous forms. If a function has a callback, then it should be treated as asynchronous, and thus candidate for async/await, otherwise it should be treated as synchronous by default. The BTW the current readme has this to say on this specific function:
Issue #511 also touches on whether |
maybe you could deprecate jwt.verify and create 2 distinct methods with explicit contracts
I think this is non breaking and it becomes obvious which one to call in your code (and which one is going to introduce a real Promise)
in my personal use case, I'd like the following
|
Why can't |
haven't read everything. Some say that crypto is sync, so that there is no need for any callback or promises. that is if we are able to one day switch away from using node Buffer and replacing it with Uint8Array instead |
Methods should return a Promise.
The text was updated successfully, but these errors were encountered: