Why is jwt auth querying for user multiple times, when it should be querying zero times?

Using a stripped down controller.action

  async do ({request}) {
    console.log('noop');
    return {'results': []};
  }

I can see in the logs that the user database is being called multiple times

  adonis:auth finding user with primary key as 1 +24s
  knex:query select * from "users" where "id" = ? limit ? undefined +24s
  adonis:auth attempting to authenticate via ["jwt"] scheme(s) +24s
  adonis:auth finding user with primary key as 1 +24ms
  knex:query select * from "users" where "id" = ? limit ? undefined +20ms
  adonis:auth authenticated using jwt scheme +11ms
noop
info: POST 200 /api/search 56ms

If I switch to session auth its only called one time

  adonis:auth finding user with primary key as 1 +0ms
  knex:query select * from "users" where "id" = ? limit ? undefined +0ms
  adonis:auth attempting to authenticate via ["session"] scheme(s) +0ms
  adonis:auth authenticated using session scheme +0ms
noop
info: POST 200 /api/search 418ms

That looks weird enough, but neither of these auth schemes should be hitting the database at all.

The uid will be in the cookie or the jwt token, and auth.getUser should be making the query, if necessary.

Shouldn’t the uid be available without the user querying the database at all.

Since Adonisjs gives you the user object for the authenticated user, it has to query the database for that.

Regarding multiple queries with Jwt, that seems to be weird, lemme try to reproduce it

I’m surprised that this isn’t the way to query the database and get the user object, its all through the docs

const user = await auth.getUser()

Because auth queries behind the scenes its potentially doubling the load on the database. Under a high load api this would be brutal.

This is even more surprising since auth.getUser() is async in the docs, yet it seems to be returning something that has already been read from the database.

I think that getUser() should query the database and fill the user object itself. But we need another method to get the uid. Something like getUserId()

You might be thinking that every method that uses auth is going to chain all its queries off the user object

In my app I have this endpoint. It will be high traffic and needs to use knex outside of lucid because pluck doesn’t work correctly with lucid

  async tags ({auth}) {
    const user    = await auth.getUser();
    const results = Database.table('tags').pluck('name').where({'user_id': user.id});
    return results;
  }

As of right now its making two queries (actually 3 - reason for this post), one of which is for the uid which it already has. It should only have to hit the database once.

Let’s discuss one thing at a time.

Because auth queries behind the scenes its potentially doubling the load on the database.

I don’t think, that it will run multiple queries when executing auth.getUser, coz it does check, if the user has already been loaded, then return it as it is. https://github.com/adonisjs/adonis-auth/blob/develop/src/Schemes/Session.js#L309

Under a high load api this would be brutal.

It depends what you are optimizing for. A simple select query is okay to make sure that the user entering system does exists.
How would you handle the situation, where a user is removed from the database post login?

I think that getUser() should query the database and fill the user object itself. But we need another method to get the uid. Something like getUserId()

You have the user model instance and you can read all the properties from it.

You might be thinking that every method that uses auth is going to chain all its queries off the user object

Didn’t get, what you mean here

1 Like

Because auth queries behind the scenes its potentially doubling the load on the database.

I don’t think, that it will run multiple queries when executing auth.getUser , coz it does check, if the user has already been loaded, then return it as it is. https://github.com/adonisjs/adonis-auth/blob/develop/src/Schemes/Session.js#L309

Yes you are right it caches so user is only loaded once from the database. But my point is that if you have an endpoint that runs one query ( like many do ), but because of the way auth works, now you’ve got two queries (on AWS that could cost something).

But I have no idea why auth.getUser() returns a promise if its already been queried. And there’s also an auth.user that isn’t async

How would you handle the situation, where a user is removed from the database post login?

I’ve never though of doing anything like that, it would probably make a mess somewhere and you’d have no record of that user ever existing. I think a better practice would be to have a blacklist or just a flag in the table.

Is this the only reason that each authenticated endpoint has to hit the database for the user? Just in case the user is deleted from the database?

If that is the case, I think a better solution would be for auth.getUser() to be lazy loaded, and provide a auth.getUserId() which just returns the current UID without hitting the database.

1 Like