Custom Access Tokens in Laravel Sanctum cover image
Image by Kelly Sikkema

I recently had a Laravel project in which I wanted a simple access token to protect an API; and Laravel Sanctum is a package that provides exactly that. However, by default, this package had a bit of a shotgun approach for validating these access tokens. You could either:

  1. Let every token live forever (default behavior)
  2. Make every token expire after a certain amount of minutes

Since there was no middle ground I created a PR that allowed for more control over these validation rules. This PR has since been released in Laravel Sanctum 2.11.0.

Let's have a look at how you can build your own custom validation rules.

Registering a custom validation with Sanctum

To register a custom validation we can instruct Sanctum to use a callable via the Sanctum::authenticateAccessTokensUsing() method. You should call this method in the boot() method of one of your service providers. The callable will contain your custom logic, and will typically be:

  • A callback function
  • An __invoke-able class
  • A (method on a) service from the IoC container

In these examples we'll use a callback function, and register them in the AppServiceProvider::boot() method.

use Laravel\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;

class AppServiceProvider
{
    // ...
    public function boot()
    {
        // ...
        Sanctum::authenticateAccessTokensUsing(
            static function (PersonalAccessToken $accessToken, bool $is_valid) {
               // Your custom logic goes here
            }
        );
    }
}

As you can see, the callable receives the provided access token model instance, as well as a boolean indicating whether the Guard from Sanctum deemed this token valid.

Tip: If the Guard could not find the provided access token, the callback method will not be triggered. So our callable will always receive an instance of the provided access token.

Tip 2: The access token model does not have to be an instance of PersonalAccessToken, because you can set a different model by using the Sanctum::usePersonalAccessTokenModel() method.

Now that we can add our own validation, let's look at a couple of useful examples.

Single-use access tokens

You might want to provide an access token that can be used only once and has a limited scope. So lets say, when our token has a read:once ability, we only want this token to be used once.

Sanctum::authenticateAccessTokensUsing(
    static function (PersonalAccessToken $accessToken, bool $is_valid) {
        if (!$accessToken->can('read:once')) {
            return $is_valid; // We keep the current validation.
        }

        return $is_valid && $accessToken->last_used_at === null;
    }
);

After making sure the token has the read:once ability, the callback does two simpel checks:

  • Does Sanctum deem this token valid?
  • Is the last_used_at value still empty (indicating it has not been used yet)?

If both of these checks pass, the token is valid. Otherwise, it was already invalid, or it has been used before.

To hand out these tokens, you can create them like this:

return $user->createToken('token-name', ['read:once'])->plainTextToken;

Short-lived tokens with variable lifetimes

Obviously you can already create short-lived tokens in Sanctum by default; the only drawback is that every token has to be short-lived. However, we can now create short-lived tokens while also using limitless tokens.

Using the same approach as before, we are going to create a token with a read:limited ability. Those tokens are allowed to be used for 30 minutes.

Sanctum::authenticateAccessTokensUsing(
    static function (PersonalAccessToken $accessToken, bool $is_valid) {
        if (!$accessToken->can('read:limited')) {
            return $is_valid;
        }

        return $is_valid && $accessToken->created_at->gt(now()->subMinutes(30));
    }
);

We're doing almost the same check, but now we make sure our token was created within the last 30 minutes. Otherwise, it isn't valid.

Endless possibilities

These two examples are very simple, but the possibilities for (re)validating your access tokens are almost limitless now. I would recommend using a dedicated class for these rules to keep your boot() method clean.

Share this article

Twitter Facebook LinkedIn