0

I have an async function:

const jwtDecode = async (token) => {
    //return jsonwebtoken.verify(token, TOKEN_SECRET)
    console.log("jwt decode, token: " + token)

    try {
        const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(TOKEN_SECRET))

        return payload
    }
    catch (e) {
        console.log("Caught error jwt decode")
        console.error(e.message, e.stack)
        return {}
    }
}

That function is wrapped by a few other async functions, where await is used.

const foo = async (token) => {
    const {userId} = await jwtDecode(token)

    return userId
}

const foo2 = async (token) => {
    const userId = await foo(token)

    return userId
}

const userId = await foo2(token)

When an exception is thrown in jwtDecode, then the whole chain seems to forget about await and executes as if it didn't exist. On top of that, the call to foo2 returns a Promise instead of 'undefined'

I know there is a logical explanation to this. Could someone explain and provide the solution? Thanks

UPDATE:

Here is the full code:

This first call happens in my _middleware.js (I am using Next.js)

export async function middleware(req, ev) {
    ...
    ...
    ...

    const userId = await serverUtils.authorizeRequest(req)

    ...
    ...
    ...
const authorizeRequest = async (req) => {
    const accessToken = req.cookies.PictosAT

    let { userId } = await jwtDecode(accessToken)

    if (!userId) {
        userId = null
    }
    console.log("Authorize request userId is " + userId)
    return userId
}

const jwtDecode = async (token) => {

    console.log("jwt decode, token: " + token)


    try {
        const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(TOKEN_SECRET))

        return payload
    }
    catch (e) {
        console.log("Caught error jwt decode")
        console.error(e.message, e.stack)
        return {}
    }
}
Cb32019
  • 185
  • 1
  • 13
  • Well how does your `middleware` function handle the case of `userId` being `null`? – Bergi May 01 '22 at 00:20
  • "*the whole chain seems to forget about await and executes as if it didn't exist.*" - what do you mean by this? Can you share the logs of the execution of your code? Where exactly do you get an unexpected value (and which one)? – Bergi May 01 '22 at 00:22
  • "*On top of that, the call to `foo2` returns a Promise instead of 'undefined'*" - well [it always does](https://stackoverflow.com/q/43422932/1048572), that's expected. It's a promise for `undefined`, though, and since you `await` it it should work as always – Bergi May 01 '22 at 00:23

1 Answers1

0

You're not catching at the proper place. You need the consumers of jwtDecode to be able to determine if the function succeeded or not. With your current approach, return {} means that at the top of the chain, const userId = await foo2(token) will assign undefined to userId. If you catch inside jwtDecode, that means that its consumers can't see that there was an error.

Figure out where the error can be meaningfully caught. If you want this line:

const userId = await foo2(token)

to not run at all if there's a problem, then either omit the catch entirely so the error is caught even higher up the chain, or re-throw the error inside jwtDecode so other functions can see that it failed.

Another option is to catch at the point of assignment to userId, then check if it's truthy or not.

const jwtDecode = async (token) => {
    const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(TOKEN_SECRET))
    return payload
}
const foo = async (token) => {
    const {userId} = await jwtDecode(token)
    return userId
}

const foo2 = async (token) => {
    return foo(token);
}

// resolve to undefined if there was an error:
const userId = await foo2(token).catch(() => {});
if (!userId) {
  // put your desired error handling here...
  return;
}
// continue on with code that depends on the populated userId here
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • actually i dont want callers of foo2 to know whether an exception occurred or not and to handle it. if userId is undefined that means the user is not logged in. Thanks for your advice on the structure, but i still dont understand why the async/await situation is disregarded when i return {}. do i need to wrap that in a promise? – Cb32019 Apr 30 '22 at 14:04
  • It's not that it's disregarded, it's that `jwtDecode` resolves to an empty object if there's an error, and `userId` should be `undefined` when that happens. That should be what your original code is doing - if that's actually what you want, I don't understand the question. – CertainPerformance Apr 30 '22 at 14:12
  • The problem is that when an exception occurs in jwtDecode, the function call: const userId = await foo2(token) has two issues. First, the value returned from foo2 is not undefined it's a Promise object. The second issue is that the foo2 call does not wait for all the nested function calls to finish but instead continues – Cb32019 Apr 30 '22 at 18:54
  • Async functions always return Promises, no matter whether it resolved or rejected. Because the `jwtVerify` function is asynchronous, there's no way around having `jwtDecode` always returning a Promise (and thus, everything up the chain consuming a Promise). But that should be OK, because you're `await`ing everything properly. The code in the question looks to be running in the proper order when there's an error, see here: https://jsfiddle.net/5nL3aucg/ – CertainPerformance Apr 30 '22 at 20:34
  • yeah i understand every async returns a promise, but the problem is that even though I am using "await" before the call to foo2, it's not "awaiting" when an exception is thrown. even thought I am using await, foo2 is returning a promise object only when an exception is thrown in jwtDecode – Cb32019 Apr 30 '22 at 21:40
  • I can't reproduce it. Is the code you're actually using exactly the same as it is in the question? You may be leaving something essential out. Using the code in the question, the flow looks to be as expected. – CertainPerformance Apr 30 '22 at 21:42
  • yeah i understand every async returns a promise. I saw your code and it is running as expected. but that is not what is happening with my code. That's my whole point. What I am seeing, using your example to illustrate would be "done, userId is ", [Promise object] "Caught error jwt decode" As I've stated before, the "await" statement doesn't have an effect. The code doesn't "wait" and that's why you see "done..." before "Caught error jwt decode" – Cb32019 Apr 30 '22 at 21:45
  • I updated the post with more code, just in case I missed anything important. – Cb32019 Apr 30 '22 at 21:52
  • `const userId = serverUtils.authorizeRequest(req)` is missing the `await` that foo2 has. You must use `await` (or `.then`) to wait for Promises to resolve. You need `const userId = await serverUtils.authorizeRequest(req)` – CertainPerformance Apr 30 '22 at 21:55