DEV Community

Cover image for Creating Refresh Tokens with Laravel Sanctum
Muhammad Hassan
Muhammad Hassan

Posted on • Edited on • Originally published at muhamadhhassan.me

2

Creating Refresh Tokens with Laravel Sanctum

Laravel Sanctum is a lightweight authentication package for SPA applications and APIs. It was released in 2020 and became available out of the box since Laravel 8. Unlike JWT self-contained tokens, Sanctum uses a reference token.

What is a Reference Token?

A reference token is a type of authentication token that contains a reference to the actual data stored on the server side in some data storage. This reference can be used to look up the actual data and verify the authenticity of the token.
Sanctum uses the database as its storage, and if we take a look at the Sanctum personal_access_tokens database table, we notice that it contains information about the eloquent model that is being authenticated, the token abilities when it was last used and its expiration timestamp.

public function up(): void
{
    Schema::create('personal_access_tokens', function (Blueprint $table) {
        $table->id();
        $table->morphs('tokenable');
        $table->string('name');
        $table->string('token', 64)->unique();
        $table->text('abilities')->nullable();
        $table->timestamp('last_used_at')->nullable();
        $table->timestamp('expires_at')->nullable();
        $table->timestamps();
    });
}
Enter fullscreen mode Exit fullscreen mode

Advantages of Reference Tokens

  • Reference tokens can be preferred in scenarios where the token payload is too large to be included directly in the token.
  • Since reference tokens do not include all the necessary data, the risk of data breaches is reduced.
  • The token data can be updated or changed without having to invalidate the old token and issue a new one to the client. This can also lead to better scalability since the number of issued and managed tokens is reduced.
  • Being smaller in size compared to self-contained tokens, they can be transmitted and processed faster.

Why Refresh Tokens Are Needed?

The purpose of Refresh Tokens is to obtain long-term access to an API on behalf of the user which provides a better user experience as users do not need to re-enter their credentials every time their access token expires.

So, why do not we issue access tokens with a long time-to-live (TTL)? While extending the TTL of an access token may seem like a simple solution, it can increase the risk of security breaches. The longer an access token is valid, the longer a compromised token can be used to access the user's data until it expires.

Issuing Refresh Tokens with Sanctum

1. Configure Time-to-Live Values

There is a difference in the time-to-live between access tokens and refresh tokens but Sanctum has only one configuration for expiration in config/sanctum.php. Let's start by adding another configuration for refresh tokens expiration named rt_expiration

<?php

return [
    .
    .
    .
    'expiration' => 60,              // One hour
    'rt_expiration' => 7 * 24 * 60,  // 7 Days
];
Enter fullscreen mode Exit fullscreen mode

2. Overriding Sanctum Service Provider

Although the new rt_expiration configuration to create the refresh tokens, Sanctum will always use the expiration value to check on the token validity. In Laravel\Sanctum\SanctumServiceProvider, the request guard is created using the default expiration:

/**
 * Register the guard.
 *
 * @param  \Illuminate\Contracts\Auth\Factory  $auth
 * @param  array  $config
 * @return RequestGuard
 */
protected function createGuard($auth, $config)
{
    return new RequestGuard(
        new Guard($auth, config('sanctum.expiration'), $config['provider']),
        request(),
        $auth->createUserProvider($config['provider'] ?? null)
    );
}
Enter fullscreen mode Exit fullscreen mode

The TTL value should be selected based on the incoming request. First, let’s define a new configuration for the refresh token URL pattern:

<?php

return [
    'rt_url_pattern' => env('SANCTUM_RT_URL_PATTERN', '*/auth/refresh-token'),
];
Enter fullscreen mode Exit fullscreen mode

Then, create a new service provider:

php artisan make:provider SanctumServiceProvider
Enter fullscreen mode Exit fullscreen mode

This provider will extend the package’s provider and it will have the new implementation of the createGuard method:

<?php

namespace App\Providers;

use Illuminate\Auth\RequestGuard;
use Illuminate\Contracts\Auth\Factory;
use Laravel\Sanctum\Guard;

class SanctumServiceProvider extends \Laravel\Sanctum\SanctumServiceProvider
{
    /**
     * Register the guard.
     *
     * @param  Factory  $auth
     * @param  array<string, mixed>  $config
     */
    #[\Override]
    protected function createGuard($auth, $config): RequestGuard
    {
        $expiration = request()->is(config('sanctum.rt_url_pattern'))
            ? config('sanctum.rt_expiration')
            : config('sanctum.expiration');

        return new RequestGuard(
            new Guard($auth, $expiration, $config['provider']),
            request(),
            $auth->createUserProvider($config['provider'] ?? null)
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

The final step is to tell Laravel not to load the package’s service provider. In composer.json, update the the extra property as follows:

"scripts": {
   "extra": {
        "laravel": {
            "dont-discover": [
              "laravel/sanctum"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Define Token Abilities

The refresh token should have one ability, which is issuing new access tokens. One way to do this is by creating an enumeration for token abilities at app\Enums\TokenAbility.php

<?php

namespace App\Enums;

enum TokenAbility: string
{
    case ISSUE_ACCESS_TOKEN = 'issue-access-token';
    case ACCESS_API = 'access-api';
}
Enter fullscreen mode Exit fullscreen mode

Now, when a user login two tokens will be issued

use Illuminate\Http\Request;
namespace App\Enums\TokenAbility;

Route::post('/auth/login', function (Request $request) {
    // Credentials check should go here.

    $accessToken = $user->createToken('access_token', [TokenAbility::ACCESS_API->value], config('sanctum.expiration'));
    $refreshToken = $user->createToken('refresh_token', [TokenAbility::ISSUE_ACCESS_TOKEN->value], config('sanctum.rt_expiration'))

    return [
        'token' => $accessToken->plainTextToken,
        'refresh_token' => $refreshToken->plainTextToken,
    ];
});
Enter fullscreen mode Exit fullscreen mode

To use the token abilities in protecting routes, we need to add Sanctum abilities middlewares. In Laravel 10.X and prior, the middlewares should be added to the $middlewareAliases property in app/Http/Kernel.php file:

'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
Enter fullscreen mode Exit fullscreen mode

Starting from Laravel 11.X, middlewares configuration is part of the application bootstrap file at ./bootstrap/app.php:

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->alias([
            'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
            'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
        ]);
    })
    ->create();
Enter fullscreen mode Exit fullscreen mode

4. Assign the Ability Middleware to Routes

Finally, the route for issuing a new access token should be protected by the ability middleware

use Illuminate\Http\Request;
namespace App\Enums\TokenAbility;

Route::post('/auth/refresh-token', function (Request $request) {
    $accessToken = $request->user()->createToken('access_token', [TokenAbility::ACCESS_API->value], config('sanctum.expiration'));

    return ['token' => $accessToken->plainTextToken];
})->middleware([
    'auth:sanctum',
    'ability:'.TokenAbility::ISSUE_ACCESS_TOKEN->value,
]);
Enter fullscreen mode Exit fullscreen mode

In summary, refresh tokens can make your application more secure but because they are powerful authentication artifacts, they should be kept secure. You can learn more about securing refresh tokens from this article.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)