Implementing cache on tymondesign/jwt-auth with Laravel/Lumen

Syed Sirajul Islam Anik
4 min readMar 10, 2020

--

image from: http://bit.ly/2W0MfFZ

Just self-promotion, if you don’t know how Laravel/Lumen resolves the Auth, then you should have a look in this article. Laravel Auth — how does it happen? And if you know that, but if you want to know how to implement your own and custom auth system, then this article may help you to implement. Laravel API — Authenticate user with custom driver & different table using auth middleware.

To extend tymondesign/jwt-auth, you don’t need to know much about Laravel’s auth system thoroughly. But it’s always good to know how things work. It can help you to tweak things when you’re having a hard time.

What’s the problem?

As you somehow reached here, it can have two reasons. Either you want to know something new or you’re here because you want to optimize a problem. After you successfully set up the package and everything, whenever you want to get the currently authenticated user for the current request using jwt driver's auth('api')->user() method you may or may not notice that the package always queries the database for that user for a valid token if the user is not previously fetched. Previously fetched means for the currently serving request. The next request for the same user will fetch the user from database again.

Adding the following line of code in any of the service provider’s boot method, you’ll see a log written in your log file. It’ll work for both Laravel/Lumen projects. It’s for debugging purposes. Remove it from your production.

app('db')->listen(function($query) {
app('log')->info(
$query->sql,
$query->bindings,
$query->time
);
});

Now, if you want to get the auth('api')->user() with a valid token. you’ll find something like below.

logging database query

So, if you always hit the database for querying a user then it’s not an optimized solution. Rather you should serve it from the cache which is faster than making queries to the database.

Any solution to the problem?

Yes.

Previously mentioned articles thoroughly describe how the auth system works. To retrieve the user from the cache with the package, you will need to do things like the following.

  • You need to create a new provider which extends Illuminate\Auth\EloquentUserProvider and override the retrieveById method.
<?php// Place the class wherever you think is good
namespace App\Extensions;

use Illuminate\Auth\EloquentUserProvider;

class CacheEloquentProvider extends EloquentUserProvider
{
public function retrieveById ($identifier)
{
// implement cache however you like
// following is for simplicity.
$user = app('cache')->get($identifier);
if (!$user) {
$user = parent::retrieveById($identifier);
if ($user) {
app('cache')->put($identifier, $user);
}
}

return $user;
}
}
  • In your App\Providers\AuthServiceProvider’s boot method, add new provider driver like the following snippet. For Lumen, App\Providers\AuthServiceProvider is not added by default. Register this provider in your bootstrap/app.php file.
$this->app['auth']->provider('cached-user-driver', 
function ($app, $config) {
// use App\Extensions\CacheEloquentProvider;
return new CacheEloquentProvider(
$this->app['hash'],
$config['model']
);
});
  • In your config/auth.php file, do like the following.
...
'guards' => [
...
'api' => [
...
// update below line
'provider' => 'cached-user',
],
],
...
'providers' => [
...
// add the following array
'cached-user' => [
'driver' => 'cached-user-driver',
'model' => App\User::class,
],
...
],
...

Now, you’re good to go. The bold texts for string and classes are given to highlight the names. And you can use any text you want. Whatever makes sense, you can use that. And, you can implement the cache in your own way. Here in the article, it’s simply written for a demonstration.

Output!!

After implementing the cache first behavior to fetch a user, it’ll only query when the user is not available in your cache. If retrieved from the database, then it’ll put the object in the cache. Which in turn will reduce the database queries. And as soon as the object is in the cache, it’ll serve from there for all the upcoming requests for that user.

Serves from cache instead of querying DB.

CON?!?!

Obviously there is a con. As you’re caching the user object, you must have to make sure that the cache expires somehow. Otherwise, if the user changes his object from some endpoint and you don’t update the object in your cache, then that will lead to an error. So you should always bear in mind that the cache MUST HAVE to be expired somehow.

Another self-promotion

It’s always good to use events. If a user updates his profile, then it’s good to shoot an event and clear cache for that user from that event handler. And you can also perform other tasks from there too. You can have a look for events & queues here. Laravel — Event & Queue.

Happy coding ❤

Edits

Someone on Reddit replied with a better version of what’s done here using the decorator pattern. Please follow THE LINK if you’re interested.

--

--

Syed Sirajul Islam Anik

software engineer with "Senior" tag | procrastinator | programmer | !polyglot | What else 🙄 — Open to Remote