Autenticació

Els fonaments de l'autenticació a Laravel: guards, providers, login manual, remember me i protegir rutes.

Com funciona l'autenticació#

El sistema d'autenticació de Laravel es basa en dos conceptes: guards i providers. Un guard defineix com s'autentiquen els usuaris per a cada petició. Per exemple, el guard session utilitza cookies de sessió per identificar l'usuari, mentre que el guard token utilitza un token a la capçalera de la petició. Un provider defineix d'on es recuperen els usuaris: normalment d'una taula de la base de dades amb Eloquent.

Configuració#

La configuració d'autenticació es troba a config/auth.php. Per defecte, Laravel configura un guard web basat en sessions i un provider users que utilitza el model User:

// config/auth.php
'defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],
 
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],
 
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],

El guard web utilitza sessions PHP per mantenir l'estat d'autenticació entre peticions. El provider users utilitza Eloquent per recuperar els usuaris de la taula users. Pots afegir guards addicionals per a diferents tipus d'usuaris o mecanismes d'autenticació.

Múltiples guards#

Si la teva aplicació té diferents tipus d'usuaris (per exemple, clients i administradors), pots crear guards separats:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],
 
'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ],
],

Per utilitzar un guard específic:

// Autenticar amb el guard admin
Auth::guard('admin')->attempt($credentials);
 
// Obtenir l'usuari del guard admin
$admin = Auth::guard('admin')->user();

Autenticació manual#

Tot i que els starter kits com Breeze i Jetstream proporcionen una implementació completa, és important entendre com funciona l'autenticació manualment.

Login#

El mètode attempt() comprova les credencials contra la base de dades. Si la contrasenya coincideix, crea una sessió autenticada:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
 
public function login(Request $request)
{
    $credentials = $request->validate([
        'email' => ['required', 'email'],
        'password' => ['required'],
    ]);
 
    if (Auth::attempt($credentials)) {
        $request->session()->regenerate();
 
        return redirect()->intended('dashboard');
    }
 
    return back()->withErrors([
        'email' => 'Les credencials no coincideixen amb els nostres registres.',
    ])->onlyInput('email');
}

El mètode regenerate() regenera l'ID de sessió per prevenir atacs de fixació de sessió. El mètode intended() redirigeix l'usuari a la URL que intentava visitar abans de ser redirigit al login, o a l'URL per defecte si no n'hi havia cap.

Pots afegir condicions addicionals a l'intent d'autenticació:

// Només usuaris actius
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => true])) {
    // Login correcte
}

Remember me#

Per mantenir l'usuari autenticat entre sessions del navegador, passa true com a segon paràmetre d'attempt():

if (Auth::attempt($credentials, $request->boolean('remember'))) {
    // L'usuari es mantindrà autenticat
}

Això crea un token de "remember me" a la base de dades i una cookie persistent al navegador. La taula users necessita la columna remember_token (que ve per defecte a la migració d'usuaris de Laravel).

Logout#

public function logout(Request $request)
{
    Auth::logout();
 
    $request->session()->invalidate();
    $request->session()->regenerateToken();
 
    return redirect('/');
}

La seqüència és important: primer tanques la sessió d'autenticació, després invalides la sessió HTTP i finalment regeneres el token CSRF. Això garanteix que la sessió queda completament netejada i protegida.

Autenticar una instància d'usuari#

Si ja tens una instància del model User (per exemple, després d'un registre), pots autenticar-la directament:

// Login directe amb un model User
Auth::login($user);
 
// Login amb remember me
Auth::login($user, remember: true);
 
// Login per ID
Auth::loginUsingId(1);

Accedir a l'usuari autenticat#

Hi ha diverses maneres d'obtenir l'usuari autenticat:

use Illuminate\Support\Facades\Auth;
 
// Amb la facade Auth
$user = Auth::user();
$id = Auth::id();
 
// Des de la petició
$user = $request->user();
 
// Comprovar si està autenticat
if (Auth::check()) {
    // L'usuari ha iniciat sessió
}
 
// Comprovar si és un convidat (no autenticat)
if (Auth::guest()) {
    // No està autenticat
}

A les vistes Blade, pots utilitzar les directives @auth i @guest:

@auth
    <p>Hola, {{ auth()->user()->name }}!</p>
    <form method="POST" action="/logout">
        @csrf
        <button type="submit">Tancar sessió</button>
    </form>
@endauth
 
@guest
    <a href="/login">Iniciar sessió</a>
    <a href="/register">Registrar-se</a>
@endguest

Si tens múltiples guards:

@auth('admin')
    <p>Ets un administrador</p>
@endauth

Protegir rutes#

El middleware auth redirigeix els usuaris no autenticats al formulari de login:

// Ruta individual
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware('auth');
 
// Grup de rutes
Route::middleware('auth')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
    Route::get('/profile', [ProfileController::class, 'edit']);
    Route::resource('articles', ArticleController::class);
});

Per redirigir a una ruta específica en lloc del login per defecte, personalitza el middleware al fitxer bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo('/login');
})

Protegir amb un guard específic#

Route::middleware('auth:admin')->group(function () {
    Route::get('/admin/dashboard', [AdminController::class, 'index']);
});

Confirmació de contrasenya#

Per a accions sensibles (canviar el correu, eliminar el compte), pots requerir que l'usuari confirmi la seva contrasenya abans de continuar:

Route::get('/settings', function () {
    // L'usuari ha de confirmar la contrasenya
})->middleware(['auth', 'password.confirm']);

Quan l'usuari visita una ruta amb aquest middleware, Laravel el redirigeix a un formulari de confirmació de contrasenya. Un cop confirmada, l'usuari pot accedir a la ruta durant un temps configurable (per defecte, 3 hores).

Limitar intents de login#

Per protegir contra atacs de força bruta, pots limitar els intents de login amb el trait ThrottlesLogins o manualment amb el RateLimiter:

use Illuminate\Support\Facades\RateLimiter;
 
public function login(Request $request)
{
    $throttleKey = Str::lower($request->email) . '|' . $request->ip();
 
    if (RateLimiter::tooManyAttempts($throttleKey, 5)) {
        $seconds = RateLimiter::availableIn($throttleKey);
 
        return back()->withErrors([
            'email' => "Massa intents. Torna a provar-ho d'aquí {$seconds} segons.",
        ]);
    }
 
    if (Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::clear($throttleKey);
        $request->session()->regenerate();
 
        return redirect()->intended('dashboard');
    }
 
    RateLimiter::hit($throttleKey, 60); // Bloquejar durant 60 segons
 
    return back()->withErrors([
        'email' => 'Les credencials no coincideixen.',
    ]);
}

Hashing de contrasenyes#

Laravel utilitza bcrypt per defecte per hashear les contrasenyes. Mai guardis una contrasenya en text pla:

use Illuminate\Support\Facades\Hash;
 
// Hashear una contrasenya
$hashed = Hash::make('secret');
 
// Verificar una contrasenya contra un hash
if (Hash::check('secret', $hashedPassword)) {
    // La contrasenya coincideix
}
 
// Comprovar si cal rehashear (si l'algorisme o els rounds han canviat)
if (Hash::needsRehash($hashedPassword)) {
    $newHash = Hash::make('secret');
}

El model User de Laravel hasheja automàticament la contrasenya gràcies al cast hashed:

protected function casts(): array
{
    return [
        'password' => 'hashed',
    ];
}