Creating Refresh Tokens with Laravel Sanctum

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();
    });
}

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

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
];

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';
}

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,
    ];
});

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,

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();

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,
]);

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.