Introduction to Token Authentication
Before the advent of token-based systems, authentication in web applications primarily relied on server-side sessions. This worked well in monolithic environments, where the backend and frontend were part of the same application, but it posed challenges in modern architectures with multiple clients (mobile applications, SPAs, microservices, etc.).
Why Use Access Tokens and Refresh Tokens?
The traditional session-based authentication approach has several limitations:
• Scalability: Requires server-side storage for each active session.
• Distribution: Does not work well in distributed architectures without sharing state between servers.
• Expiration Handling: Sessions often last too long or require additional mechanisms for renewal.
Access and refresh tokens solve these issues by providing:
• Stateless authentication: Each request sends a signed token, eliminating the need to store sessions on the backend.
• Enhanced security: Access tokens have a short lifespan, and refresh tokens allow obtaining new access tokens without requiring user credentials.
• Microservices compatibility: Facilitates authentication in distributed systems, enabling multiple services to verify identity without accessing a central database.
Token Protection Middleware
The TokenAccessGuard middleware is responsible for verifying whether a token has the appropriate permissions to perform an action. Let’s take a look at its implementation:
namespace App\Http\Middleware;
use App\Enums\TokenAbilityEnum;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class TokenAccessGuard
{
public function handle(Request $request, Closure $next): Response
{
$user = Auth::user();
if (empty($user)) {
return $next($request);
}
$token = $user->currentAccessToken();
if ($token) {
if ($request->routeIs('refresh-token') && $token->cant(TokenAbilityEnum::ISSUE_ACCESS_TOKEN->value)) {
return response()->json([
'message' => 'Unauthorized',
'error' => 'Token can not issue access token',
], 401);
} elseif (!$request->routeIs('refresh-token')) {
if ($token->cant(TokenAbilityEnum::ACCESS_API->value)) {
return response()->json([
'message' => 'Unauthorized',
'error' => 'Token can not access api',
], 401);
}
}
}
return $next($request);
}
}
Middleware explanation
• Retrieves the authenticated user.
• Verifies if the token has the necessary permissions:
• If the route is refresh-token, the token must have the ISSUE_ACCESS_TOKEN ability.
• For all other routes, the token must have the ACCESS_API ability.
• If the token does not meet the required permissions, it returns a 401 Unauthorized error.
Refresh Token endpoint
This endpoint allows users to obtain a new access token using a valid refresh token:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Enums\TokenAbilityEnum;
use Illuminate\Support\Facades\Route;
Route::post('/refresh-token', function (Request $request) {
$user = Auth::user();
if (!$user) {
return response()->json(['message' => 'Unauthorized'], 401);
}
$token = $user->currentAccessToken();
if ($token->cant(TokenAbilityEnum::ISSUE_ACCESS_TOKEN->value)) {
return response()->json([
'message' => 'Unauthorized',
'error' => 'Token can not issue access token',
], 401);
}
$newAccessToken = $user->createToken('access_token', [TokenAbilityEnum::ACCESS_API->value]);
return response()->json([
'access_token' => $newAccessToken->plainTextToken,
]);
});
Endpoint Explanation
• Retrieves the authenticated user.
• Verifies if the current token has the ability to issue new access tokens.
• If valid, generates and returns a new access token.
Conclusion
Implementing access tokens and refresh tokens in Laravel Sanctum is an effective strategy to enhance API security. The TokenAccessGuard middleware protects routes by ensuring that each token has the appropriate permissions, while the refresh-token endpoint allows obtaining new access tokens without requiring the user to reauthenticate.
This implementation ensures robust and flexible authentication for any API developed with Laravel.