Guida Completa a Laravel Sanctum: Autenticazione per SPA e API

Intermedio
PHP Laravel

Impara a implementare l'autenticazione sicura in Laravel utilizzando Sanctum, sia per Single Page Applications (SPA) che per API basate su token.

Pubblicato
Tag
PHP laravel API Sanctum SPA Authentication Security

Introduzione a Laravel Sanctum

Nel panorama moderno dello sviluppo web, la gestione dell'autenticazione è diventata una sfida complessa. Con l'ascesa delle Single Page Applications (SPA) create con framework come Vue.js, React o Angular, e la necessità di fornire accesso a applicazioni mobile tramite API REST, gli sviluppatori si sono trovati a dover scegliere tra diverse strategie: i classici cookie di sessione o i token JWT (JSON Web Tokens).

Laravel Sanctum nasce per risolvere questo dilemma, offrendo un sistema leggero e flessibile che permette di gestire entrambi i mondi senza la complessità di implementazioni pesanti come Laravel Passport (che implementa l'intero standard OAuth2).

Perché scegliere Sanctum? Principalmente perché semplifica l'autenticazione per le SPA utilizzando i cookie di sessione di Laravel (che sono sicuri e resistenti agli attacchi XSS se configurati correttamente), pur permettendo l'emissione di "API Tokens" per i client che non supportano i cookie, come le app mobile o i servizi di terze parti.

Come Funziona Sanctum: I Due Approcci

Prima di immergerci nel codice, è fondamentale capire che Sanctum opera in due modalità distinte, a seconda di chi è il client.

1. Autenticazione basata su Cookie (SPA)

Quando l'applicazione frontend (es. Vue) risiede sullo stesso dominio (o sotto-dominio) del backend Laravel, Sanctum utilizza l'autenticazione basata su sessione. In questo scenario, non vengono scambiati token in ogni richiesta; Laravel utilizza i propri cookie di sessione e il middleware VerifyCsrfToken per garantire che le richieste siano legittime e provengano dall'utente autenticato.

2. Autenticazione basata su Token (API)

Per le app mobile o per l'accesso programmatico, Sanctum permette di generare token di accesso personali (Personal Access Tokens). Questi token sono stringhe casuali memorizzate nel database e inviate nell'header Authorization: Bearer {token} di ogni richiesta HTTP.

Installazione e Configurazione Iniziale

Il primo passo è l'installazione del pacchetto tramite Composer. Se stai utilizzando una versione recente di Laravel, è probabile che Sanctum sia già pre-installato, ma per sicurezza eseguiamo il comando:

composer require laravel/sanctum

Dopo l'installazione, dobbiamo pubblicare la configurazione e le migrazioni del database:

php artisan vendor:publish --provider="Laravel\\Sanctum\\SanctumServiceProvider"
php artisan migrate

Configurazione del Modello User

Affinché l'utente possa generare token, è necessario aggiungere il trait HasApiTokens al modello User. Questo trait fornisce i metodi necessari per creare e rimuovere i token di accesso.

<?php

namespace App\\Models;

use Illuminate\\Foundation\\Auth\\User;
use Illuminate\\Notifications\\Notifiable;
use Laravel\\Sanctum\\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    // Resto del modello...
}

In questo modo, l'oggetto User acquisisce la capacità di gestire i propri token tramite metodi come $user->createToken('token-name').

Implementazione dell'Autenticazione via Token

Vediamo ora come implementare un sistema di login che restituisca un token. Questo è il caso d'uso classico per le App Mobile o i client esterni.

Creazione del Controller di Autenticazione

Creiamo un controller per gestire il login. L'idea è validare le credenziali e, se corrette, generare un token unico per l'utente.

<?php

namespace App\\Http\\Controllers\\Api;

use App\\Http\\Controllers\\Controller;
use Illuminate\\Http\\Request;
use App\\Models\\User;
use Illuminate\\Support\\Facades\\Hash;
use Illuminate\\Validation\\ValidationException;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
            'device_name' => 'required', // Utile per identificare da quale dispositivo viene il token
        ]);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['Le credenziali fornite sono errate.'],
            ]);
        }

        // Creazione del token. Il device_name permette all'utente di revocare token specifici in futuro.
        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
        ]);
    }

    public function logout(Request $request)
    {
        // Revoca il token corrente utilizzato per l'autenticazione
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Logout effettuato con successo']);
    }
}

Protezione delle Rotte

Per proteggere le rotte API, utilizzeremo il middleware auth:sanctum. Questo middleware verifica se la richiesta contiene un token valido o se l'utente è autenticato tramite sessione.

// routes/api.php

use App\\Http\\Controllers\\Api\\AuthController;
use Illuminate\\Support\\Facades\\Route;

Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    Route::post('/logout', [AuthController::class, 'logout']);
});

Autenticazione per SPA (Stateful)

L'autenticazione per SPA è diversa perché non vogliamo che il frontend gestisca manualmente i token (evitando di salvarli in localStorage, dove sono vulnerabili agli attacchi XSS). Sanctum utilizza invece i cookie di sessione.

Passaggi per la configurazione SPA

  1. Middleware: Aggiungi il middleware EnsureFrontendRequestsAreStateful al gruppo api nel file app/Http/Kernel.php (o nel provider di configurazione nelle versioni più recenti di Laravel).
  2. Configurazione CORS: Nel file config/cors.php, imposta supports_credentials => true per permettere l'invio dei cookie tra frontend e backend.
  3. Dominio di sessione: Nel file .env, imposta SESSION_DOMAIN=.tuodominio.com per assicurare che il cookie sia condiviso tra i sottodomini.

Il flusso CSRF

Per prevenire gli attacchi Cross-Site Request Forgery, Sanctum richiede che l'app frontend effettui prima una richiesta a un endpoint speciale: /sanctum/csrf-cookie.

Il flusso è il seguente:

  1. Il client chiama GET /sanctum/csrf-cookie.
  2. Laravel invia un cookie CSRF.
  3. Il client effettua la richiesta di login inviando il token CSRF nell'header X-XSRF-TOKEN.
  4. Se le credenziali sono corrette, Laravel avvia una sessione standard.

Esempi Pratici e Casi d'Uso

Caso 1: App Mobile (React Native/Flutter)

In questo scenario, l'utente inserisce email e password. L'app invia i dati a /api/login, riceve il plainTextToken e lo salva nel SecureStore del dispositivo. Ogni richiesta successiva includerà l'header Authorization: Bearer {token}.

Caso 2: Dashboard Aziendale (Vue.js + Laravel)

Qui l'app Vue e l'API Laravel condividono lo stesso dominio. L'app chiama /sanctum/csrf-cookie, poi effettua il login tramite POST /login (rotta web standard). Il browser gestisce automaticamente i cookie di sessione, rendendo l'esperienza utente fluida e sicura.

Caso 3: Gestione di Token Multipli

Immaginiamo un utente che accede da PC, Tablet e Smartphone. Grazie a createToken($deviceName), possiamo mostrare all'utente una lista di dispositivi connessi e permettergli di revocare l'accesso solo al Tablet se smarrito:

// Revoca tutti i token tranne quello attuale
$user->tokens()->where('id', '!=', $user->currentAccessToken()->id)->delete();

Errori Comuni e FAQ

"Unauthenticated" nonostante il token sia corretto

Questo è l'errore più frequente. Spesso accade perché:

  • L'header Accept: application/json non è stato inviato. Senza questo header, Laravel potrebbe tentare di reindirizzare l'utente alla pagina di login web invece di restituire un errore 401 JSON.
  • Il middleware auth:sanctum non è applicato alla rotta.

Dove salvare il token nel frontend?

Mai in localStorage per le app web, a causa del rischio XSS. Per le SPA, usa l'autenticazione stateful di Sanctum. Per le app mobile, usa i sistemi di storage sicuro forniti dal sistema operativo (es. Keychain su iOS, EncryptedSharedPreferences su Android).

Qual è la differenza tra Sanctum e Passport?

Passport è un server OAuth2 completo. È necessario se devi permettere a terze parti di accedere ai dati dei tuoi utenti (come quando un sito permette di "Accedere con Google"). Sanctum è invece ideale per l'autenticazione interna della tua stessa applicazione.

Prossimi Passi

Ora che hai implementato l'autenticazione di base, puoi approfondire i seguenti temi per rendere la tua applicazione professionale:

  1. Abilità dei Token (Token Abilities): Sanctum permette di assegnare permessi specifici ai token. Ad esempio, puoi creare un token che può solo "leggere" i dati ma non "scriverli": $user->createToken('token-name', ['post:read'])->plainTextToken;

  2. Gestione dell'Espirazione: Configura la durata dei token nel file config/sanctum.php per aumentare la sicurezza.

  3. Integrazione con Laravel Fortify: Per una gestione ancora più robusta di registrazione, reset password e verifica email, considera l'uso di Laravel Fortify in combinazione con Sanctum.

  4. Testing: Scrivi test automatizzati utilizzando Sanctum::actingAs() per simulare utenti autenticati durante i tuoi test di integrazione.

L'autenticazione è il cuore della sicurezza di ogni applicazione. Utilizzare uno strumento standardizzato e supportato ufficialmente come Sanctum riduce drasticamente la superficie di attacco e accelera i tempi di sviluppo.