Content Index
- What is Laravel Sanctum?
- 1 SPA Authentication
- 2 Based on API Tokens
- Generate access tokens
- More examples with Sanctum
- Strategies for Renewing the Access Token (JWT) in a REST API - Laravel Sanctum Case
- 1. Hybrid Authentication: Cookies vs. Tokens
- 2. Token Lifecycle: Mitigating Dead Records
- Strategy Zero: Scheduled Cleanup (Cron Jobs)
- Strategy One: Active Renewal (Silent Refresh)
- Strategy Two: Dynamic Token Extension
- 3. Practical Implementation: Early Validation and Session Extension
- Justification for Instance Verification
- 4. Centralization and Unique Device Identification
- Limitations of Device Control
Laravel Sanctum is a package for authenticating single-page applications (SPAs) and mobile applications that we can use to protect the Rest API on Laravel through required authentication; Laravel Sanctum and simple token-based APIs; remember that it is not as direct as adding a session, and this is because a Rest API is recommended to be stateless and this is where Laravel Sanctum comes into play. It allows each user of your application to generate multiple API tokens for their account. Laravel Sanctum provides a lightweight authentication system for SPAs and simple APIs.
Laravel Sanctum is wonderful for when we want to protect a Rest API created with Laravel either through tokens or a hybrid between sessions and cookies; before showing you how to perform the most traditional implementation, let's dive a bit into the subject and introduce what Sanctum is.
Using Laravel Sanctum we ensure that only authenticated users can access the routes that require it.
The idea behind Sanctum is to protect Laravel application APIs through authentication and access tokens. By doing so, it ensures that only authenticated and authorized requests are allowed, which in turn ensures the integrity of the application and the data involved. I hope this has helped you understand a bit more about the theory behind Laravel Sanctum.
Laravel Sanctum offers us two schemes to work with.
What is Laravel Sanctum?
Laravel Sanctum is an authentication package for Laravel that provides a simple and secure method for authenticating single-page applications (SPAs) using users and protecting your API via API tokens. Sanctum allows generating and managing API tokens, which are issued through a rest resource that we implement and passed to the client so that when they want to access protected resources, they must use said access token to connect to the resource and consume the API response.
These tokens are great because you can use them to authenticate API requests and also to restrict access to routes, since, with the token, from Laravel you know which user is trying to consume a specific resource and perform some trace or limitation.
1 SPA Authentication
This is useful when we have an SPA page and you want to add authentication to protect a Rest API and access the user; this option is based on a scheme between session authentication and cookies that we have to configure; it is worth saying that it is the simplest of the two:
At the kernel level, we have to enable the cookie and session service for Sanctum:
//App/Http/Kernel
//***
class Kernel extends HttpKernel
{
//***
'api' => [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];Specify the domain you are going to use for cookie-based authentication in the config/sanctum.php file:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
'%s%s',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,laraprimerospasos.test',
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
))),If you are using Mac or Linux with Sail, there is no need to modify this file since localhost is present; also, you can place your production domain URL.
In the config/cors.php file you have to enable supports_credentials as true.
To be able to use axios (which we will do later), you must look for the resources/js/bootstrap.js file and place:
axios.defaults.withCredentials = true;This is to enable the use of cookies with user credentials, and to be able to use it transparently with axios.
Finally, with this, we can use the middleware for route authentication; as an example, we will protect a set of routes:
Route::group(['middleware' => 'auth:sanctum'], function () {
Route::resource('category', CategoryController::class)->except(["create", "edit"]);
Route::resource('post', PostController::class)->except(["create", "edit"]);
});Otherwise, all you have to do is log in and we are ready; you can use this scheme with axios.
2 Based on API Tokens
The next scheme is more manual and in the end is a token-based authentication; for this, now when authenticating, we generate the authentication tokens:
Before starting, we are going to disable the previous Sanctum scheme for SPA authentication:
'api' => [
//\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
***
],Generate access tokens
Laravel Sanctum provides us with a simple mechanism to add authentication to Rest APIs, through Authentication Tokens or by authentication tokens; in this first entry, we will see how to use the authentication token mechanism which would be the more traditional scheme.
It is worth mentioning that it is not necessary to install Sanctum since, for a long time, Sanctum has been part of a project in Laravel.
In our rest controller, to handle the user, in the case of our Laravel course, it would be the following:
app\Http\Controllers\Api\UserController.php
We will create a login function, using the attempt function with which we can check user credentials provided in the request:
$credentials = [ 'email' => $request->email, 'password' => $request->password ];Once the credentials have been verified, we create the token and return the response:
Auth::user()->createToken('myapptoken')->plainTextToken;Finally, the complete code:
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];
if (Auth::attempt($credentials)) {
$token = Auth::user()->createToken('myapptoken')->plainTextToken;
session()->put('token', $token);
return response()->json($token);
}
return response()->json("Usuario y/o contraseña inválido", 422);
}The most important thing to note is that, although Sanctum provides all the mechanisms to generate the token, it is up to the user to implement the necessary logic to generate the token.
To create tokens for our users, we are going to create a login function that allows generating these tokens (you could also use this function in SPA authentication but without generating the token):
$ php artisan make:controller Api/UserControllerIn which we will define the following content:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function login(Request $request)
{
$credentials = [
'email' => $request->email,
'password' => $request->password
];
if(Auth::attempt($credentials)){
$token = Auth::user()->createToken('myapptoken')->plainTextToken;
return response()->json($token);
}
return response()->json("Usuario y/o contraseña inválido");
}
}Important to note that Sanctum already comes installed in modern Laravel projects.
More examples with Sanctum
Here are some other examples with authentication in Laravel Sanctum and route protection.
In case the project no longer has Sanctum, you must install Laravel Sanctum using Composer:
$ composer require laravel/sanctumThen, you will publish the Sanctum configuration and migration files:
$ php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
$ php artisan migrateNext, we can already protect our routes; for this, we use an authentication middleware in your routes file. For example:
routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::get('/post', function () {
return view('dashboard');
});
});In this example, the "/post" route will only be accessible if the user is authenticated via the Sanctum middleware.
After that, you can generate an API token for an authenticated user. For example:
$token = $user->createToken('token-name')->plainTextToken;This code will generate an API token for the given user with the name "token-name". Then, you can use the token to authenticate API requests; here is another example of login with Sanctum:
routes/api.php
use App\\Models\\User;
use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Hash;
Route::post('/login', function (Request $request) {
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$token = $user->createToken($request->device_name)->plainTextToken;
return ['token' => $token];
});That is just a basic example of how you can use Laravel Sanctum in your Laravel project. I hope it has been useful to you. If you have more questions, feel free to ask.
Strategies for Renewing the Access Token (JWT) in a REST API - Laravel Sanctum Case
Laravel Sanctum is the standard tool within the Laravel ecosystem for managing authentication in modern applications. When building hybrid platforms —which integrate web clients like Single Page Applications (SPA) in Vue and native mobile clients—, it is essential to design a solid strategy for the administration, persistence, and lifecycle of Personal Access Tokens.
Unlike traditional session schemes based exclusively on the web, the coexistence of multiple devices introduces security and performance challenges that must be resolved directly within the backend architecture.
1. Hybrid Authentication: Cookies vs. Tokens
Sanctum offers a dual approach to protecting API resources, allowing two mechanisms to be combined in parallel:
- Cookie-based Authentication (Stateful): Specifically designed for SPAs or web applications hosted on the same top-level domain. It utilizes encrypted cookies along with CSRF attack protection. This method is the one active by default for the desktop environment.
- Token-based Authentication (Bearer Tokens): Compulsory for mobile devices or third-party external services that do not share the browser's cookie storage. The client receives a plain text string after validating their credentials and must attach it to the Authorization header of every HTTP request.
The server automatically processes both pathways through Sanctum's middleware, translating the origin into a single instance of the authenticated user.
2. Token Lifecycle: Mitigating Dead Records
By default, Sanctum's configuration allows created tokens to have an indefinite expiration if the expiration directive in the config/sanctum.php file is defined as null.
config/sanctum.php
'expiration' => null, // The token does not expire by defaultAlthough this approach simplifies initial development, it represents a weakness in terms of infrastructure and security. If users log in frequently from multiple browsers or devices, the personal_access_tokens table will accumulate thousands of obsolete records ("dead records") that will slow down database queries since there is no automatic cleanup process.
To resolve this issue, there are three main design strategies:
Strategy Zero: Scheduled Cleanup (Cron Jobs)
This consists of keeping tokens without an explicit expiration date within Sanctum's configuration, but delegating the cleanup to a Laravel background process (Scheduled Tasks). The script periodically analyzes the last_used_at column and permanently deletes those records that have not registered any activity within a timeframe of several months.
Strategy One: Active Renewal (Silent Refresh)
This technique establishes a strict expiration period in the configuration (for example, 30 days). The client (whether in Vue or Flutter) has a dedicated endpoint in the API that, before reaching the deadline, invalidates the current token and generates a new one, updating the device's local storage (LocalStorage or SharedPreferences) transparently for the user.
Strategy Two: Dynamic Token Extension
This is the strategy adopted in high-availability architectures. Instead of rotating the plain text string of the token, the backend dynamically extends the validity of the record by modifying its internal metadata each time the user consumes the API. In this way, a session that is used daily never expires, but is automatically invalidated if the device stops making requests for a prolonged period.
3. Practical Implementation: Early Validation and Session Extension
Regardless of the strategy selected, the fundamental principle of client architecture dictates that the application must verify the integrity of the session upon its first launch. One should not wait for the user to attempt a critical action (such as a download or a purchase) to discover that their token has been revoked, as this degrades the user experience with unexpected cascading errors.
In both web and mobile environments, a lightweight verification endpoint (for example, /api/v1/user-info) should be consumed within the global initialization lifecycle hooks (App.vue or Flutter's main() method). If the server returns a 401 Unauthorized status code, the client intercepts the response, clears local state variables immediately, and redirects to the login form.
Below is the backend implementation detailed for a hybrid verification endpoint that extends the token's lifecycle by forcing the update of the record:
use Illuminate\Http\Request;
use Laravel\Sanctum\TransientToken;
use Laravel\Sanctum\PersonalAccessToken;
Route::middleware('auth:sanctum')->get('/v1/user-info', function (Request $request) {
$user = $request->user();
$currentToken = $user->currentAccessToken();
// We check that the token comes from the database and not from a cookie (TransientToken)
if ($currentToken instanceof PersonalAccessToken) {
/** * We force the update of the creation date.
* By moving 'created_at' to the current moment, we automatically extend
* the 30-day expiration window configured in Sanctum.
*/
$currentToken->forceFill([
'created_at' => now(),
]->save();
}
return response()->json([
'success' => true,
'data' => $user,
]);
});Justification for Instance Verification
The instanceof PersonalAccessToken validation is mandatory in hybrid infrastructures. When the user accesses from the web client using cookies, the currentAccessToken() method returns an object of type TransientToken. Since this object does not physically reside in the database table, it lacks the forceFill() method. Attempting to update it would cause a fatal error in the backend execution.
4. Centralization and Unique Device Identification
To implement restrictive business policies —such as limiting the number of simultaneous sessions allowed on shared accounts—, it is essential to centralize the token issuance logic into a single service or controller class.
When generating a token through the createToken() method, the use of generic or static strings should be avoided. Instead, the User-Agent header of the incoming HTTP request is analyzed to register the name of the browser or the native execution environment of the client:
public function generateDeviceToken(Request $request, User $user): string
{
$userAgent = $request->header('User-Agent', 'Generic Device');
$deviceName = 'Unknown Device';
// Modular classification of the request origin
if (str_contains($userAgent, 'Dart')) {
$deviceName = 'Mobile Application (Flutter)';
} elseif (str_contains($userAgent, 'Firefox')) {
$deviceName = 'Mozilla Firefox';
} elseif (str_contains($userAgent, 'Chrome')) {
$deviceName = 'Google Chrome';
} elseif (str_contains($userAgent, 'Safari')) {
$deviceName = 'Apple Safari';
}
// Length limitation to meet table schema constraints
$sanitizedName = substr($deviceName, 0, 180);
// Centralization of business rules before persistence
return $user->createToken($sanitizedName)->plainTextToken;
}Limitations of Device Control
Although registering the browser name provides clarity to the user in their settings panels and allows them to revoke specific access, it is not an infallible unique identifier for enforcing strict lockouts. If a user legitimately operates from two different computers using the same browser (for example, Google Chrome on their work computer and on their personal computer), both requests will share the same device name.
If the business logic requires blocking or terminating duplicate sessions based solely on that label, the valid session of the second device will be mistakenly closed. For scenarios requiring a unique hardware identity signature, the mobile client must extract the device's unique identifier (UUID) at the operating system level and transmit it explicitly in the authentication request parameters to the API.
Now, let's delve into the inner workings of Laravel's template engine, Blade.
Source code:
https://github.com/libredesarrollo/book-course-laravel-base-api-11/releases/tag/v0.1