When developing an API for a mobile application or a SPA with Laravel, a robust option for authentication is to use Laravel Fortify. In this article, we’ll explore how to properly handle logout for users, especially when using Laravel Sanctum for token-based authentication.
To properly handle logout, we need to create a custom controller that extends Laravel\Fortify\Http\Controllers\AuthenticatedSessionController. In this controller, we override the
destroy method to delete the user's personal access token (
PersonalAccessToken).
123use App\Models\User;4use Illuminate\Http\Request;5use Laravel\Fortify\Contracts\LogoutResponse;6use Laravel\Fortify\Http\Controllers\AuthenticatedSessionController as FortifyAuthenticatedSessionController;7use Laravel\Sanctum\PersonalAccessToken;8use Override;910final class AuthenticatedSessionController extends FortifyAuthenticatedSessionController11{12 /**13 * Destroy an authenticated session.14 *15 * @param Request $request16 * @return LogoutResponse17 */18 #[Override]19 public function destroy(Request $request): LogoutResponse20 {21 $this->guard->logout();2223 /** @var User $currentUser */24 $currentUser = $request->user();2526 $currentAccessToken = $currentUser->currentAccessToken();2728 if ($currentAccessToken instanceof PersonalAccessToken) {29 $currentAccessToken->delete();30 }3132 if ($request->hasSession()) {33 $request->session()->invalidate();34 $request->session()->regenerateToken();35 }3637 return app(LogoutResponse::class);38 }39}Code highlighting powered by torchlight.dev.
api.php
Once we have the controller, we need to add the corresponding route in the routes/api.php file:
12use Illuminate\Support\Facades\Route;3use App\Http\Controllers\Api\V1\Auth\AuthenticatedSessionController as ApiV1AuthenticatedSessionController;45// Sanctum guard routes6Route::prefix(config('fortify.prefix'))->middleware('auth:sanctum')->group(function (): void {7 Route::post('/logout', [ApiV1AuthenticatedSessionController::class, 'destroy']);8});Code highlighting powered by torchlight.dev.
This ensures that only authenticated users can access this route.
To adapt the logout response to our project's structure, we can override it in the FortifyServiceProvider.php file inside the
register method:
12namespace App\Providers;34use Illuminate\Http\JsonResponse;5use Illuminate\Http\RedirectResponse;6use Illuminate\Support\ServiceProvider;7use Laravel\Fortify\Contracts\LogoutResponse;8use Laravel\Fortify\Fortify;910class FortifyServiceProvider extends ServiceProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 Fortify::ignoreRoutes();1819 $this->app->instance(LogoutResponse::class, new class () implements LogoutResponse {20 public function toResponse($request): JsonResponse|RedirectResponse21 {22 if ($request->wantsJson()) {23 return response()->json([24 'data' => [25 'message' => 'You are successfully logged out',26 ],27 ]);28 }2930 return redirect()->intended(Fortify::redirects('login'));31 }32 });33 }34}Code highlighting powered by torchlight.dev.
Finally, it's a good practice to write automated tests to verify that the logout functionality works as expected:
12use App\Http\Controllers\Api\V1\Auth\AuthenticatedSessionController;3use Database\Factories\UserFactory;45it('should logout a user', function (): void {6 $user = UserFactory::new()->create();78 $token = $user->createToken('test-token')->plainTextToken;910 $this->actingAs($user)->postJson(action([AuthenticatedSessionController::class, 'destroy']))11 ->assertStatus(200)12 ->assertJsonStructure([13 'data' => [14 'message',15 ],16 ])17 ->assertJsonFragment([18 'message' => 'You are successfully logged out',19 ]);2021 $this->assertDatabaseMissing('personal_access_tokens', [22 'token' => $token,23 ]);24});2526it('should not logout a user if not authenticated', function (): void {27 $this->postJson(action([AuthenticatedSessionController::class, 'destroy']))28 ->assertStatus(401)29 ->assertJsonStructure([30 'message',31 ])32 ->assertJsonFragment([33 'message' => 'Unauthenticated.',34 ]);35});Code highlighting powered by torchlight.dev.
Properly handling logout is essential to ensure security and a good user experience in our API. With Laravel Fortify and Sanctum, we can efficiently revoke access tokens and customize the response according to our project's needs.