Should I fetch user id from JWT token instead of passing user_id as param?


#1

Should I use JWT token to fetch current authed user ID instead of passing user_id as a parameter?

Is that bad to passing authed user_id as a parameter? What’re the risks about?


#2

How are you implementing your JWT authentication? If you implement it with persona, for example, the payload should already include the uid and upon authentication the auth object should automatically be hydrated with your user info so that in your controller you can access relevant data about the user making the request.

No reason to overcomplicate that, in my opinion :slight_smile:

A reason you might still want to pass a user id in a parameter though would be if you wanted to allow an admin to masquerade as another user, for example, to do things on their behalf or debug issues that only a specific user has encountered… or if you’re creating content that you are attributing to another user; like a trouble ticket system, entering a ticket and assigning it to some other user, etc…

But for general auth the jWT payload probably ought to contain the uid

This is how I generate my tokens:

  /**
   * Generate JWT credentials for a valid user.
   * Returns a credentials object including a refresh token,
   * but prevents generation of excess refresh tokens.
   *
   * @param {Object} auth - Auth instance
   * @param {Object} user - User model instance
   *
   * @returns {Object} credential object - { type: "bearer", token: "...", refreshToken: "..." }
   */
  async _getCredentials(auth, user) {
    let credentials
    let tokens = await auth.listTokensForUser(user)

    if (!tokens.length) {
      credentials = await auth.withRefreshToken().generate(user)
    } else {
      credentials = await auth.generate(user)
      credentials.refreshToken = tokens[0].token
    }

    return credentials
  }

When a user initially provides their login credentials (successfully), which I verify with Persona, I return to them an object containing their JWT token, a refresh token, and all of their user data:

return {
  credentials: await this._getCredentials(auth, user),
  user: user.toJSON(),
}

That response payload looks something like this:

{
    "credentials": {
        "type": "bearer",
        "token": "eyJhbGciOiJtUzI1NiIsIvR5cCI6IkpXVCJ9.eyJ1aWQiOjU3LCJpYXQiOjE1NDQ0NjE0MTQsImV4cCI6MTU0NDQ2MjMxNH0.weesJW7vKF3_18HRoDgD6_GQ8ZN22QmMJ_cY3mvsiPk",
        "refreshToken": "8c194fcae8b43d176a1q23df26a409f7m5r4mXusx6+t+9R4Do8+zVY4Kv+DcaXa1v0Gtso8xE36VCsP9rnCeZqwG9cKdfn8"
    },
    "user": {
        "id": 57,
        "email": "jdoe@fake.io",
        "first_name": "John",
        "last_name": "Doe",
        "tos_acceptance": "2017-05-25 13:23:17",
        "disabled": false,
        "account_status": "active",
        "created_at": "2017-05-23 13:11:10",
        "updated_at": null,
        "deleted_at": null,
    }
}

The resulting token (note that is not a valid token), when decoded contains the uid along with timestamps of when the token was issued and when it expires.


#3

Thank you dear @willvincent ,

Your description was full of points. But what about security issues? Is this a security bug/mistake if I pass user_id as parameter while I can fetch it from JWT?


#4

Debatable I suppose… I don’t think so personally, what’s anyone going to do with a user id, without any other relevant data? And if they’re able to capture a user ID in flight, they’re gonna get the jwt token too – which means they’d already be able to spoof an authenticated request as that user until that token expires…

So. I probably wouldn’t worry about sending a UID in the clear, there’s not really any reason a UID needs to be secret, I don’t think…

In my opinion, it’s far more important that you enforce authorization on your routes, and use https than worry about whether a uid passed as a parameter.


#5

Thanks for your time @willvincent
I think so too, but I’m open to other ideas and answers.