Felhasználói hitelesítés (authentication) - 2. rész: Testreszabás, tippek & trükkök

Attila | 2022. 11. 09. 18:17 | Olvasási idő: 5 perc

Címkék: #Blade #Breeze #CoreConcept #E-mail #Érvényesítés (Validation) #Köztes réteg (Middleware) #Laravel #Nézet (View) #Űrlap (Form)

Az egy optimális helyzet, hogy ha a Laravel által nyújtott szolgáltatás pontosan azt adja nekünk a felhasználói hitelesítésnél, amire nekünk szükségünk van. Ebben a blogbejegyzésben viszont azokat az eshetőségeket veszem át, amikor valamit egy picit másképpen szeretnénk, mint ahogy alapértelmezetten megkapjuk a keretrendszertől.
custom-authentication-features

Bevezetés, áttekintés

Miket is szeretnénk egy picit másképp? Nézzük meg, hogy milyen módosításokat szoktak a fejlesztők a leggyakrabban elvégezni a folyamat testreszabásánál:

  1. Letiltani a regisztrációt / e-mail-es megerősítést / jelszó helyreállítást / regisztráció utáni automatikus bejelentkeztetést / egyebet. Ugrás oda...
  2. Bejelentkezés után milyen címre irányítsuk a felhasználót? Ugrás oda...
  3. Regisztrációs űrlap elemeit meg tudjuk változtatni, tipikusan az e-mail cím helyett a bejelentkezésnél majd esetleg felhasználónevet kérünk. Ugrás oda...
  4. Meg tudjuk változtatni a regisztrációs űrlap mezőinek érvényesítését (validation). Ugrás oda...
  5. Hiánypótlás: releváns Blade direktívák bemutatása. Ugrás oda...
  6. Szerepkör alapú tevékenységek: külön bejegyzésben később.


1. Letiltások

Regisztráció letiltása

Miért is szeretnénk letiltani a regisztrációt...? Hiszen, ha szeretnénk készíteni egy népszerű weboldalt, akkor elvárás lehet, hogy tudjanak regisztrálni és majd bejelentkezni a felhasználók. A tiltási igény szükségessége amiatt lehet, hogy például egy olyan webalkalmazást készítünk, amikor a hozzáféréssel rendelkező felhasználók köre szűkös, véges. Például egy kis cég adminisztrációs tevékenységeit támogató webes alkalmazásnál nincs szükség nyilvános regisztrációs felületre, mert az adminisztrátor vagy az arra jogosult személy létrehozza a felhasználókat és készen vannak.

Tiltsuk le a regisztráció lehetőségét: nyissuk meg a routes / auth.php felhasználói hitelesítéssel kapcsolatos útvonalakat tartalmazó fájlt és kommenteljük ki ezt a sort (ami egy sor, csak talán az átláthatóság miatt kettédarabolta nekünk a scaffolding):

Route::get('register', [RegisteredUserController::class, 'create'])
                ->name('register');

Ha ezután megpróbálnánk megnyitni a böngészőben a regisztrációs útvonalunkat (/register), akkor 404-es hibaoldalt kapnánk. Sőt, vegyük észre, hogy ha megnézzük az alkalmazásunk főoldalát, akkor arról is eltűnt automatikusan a jobb felső sarokból a regisztráció lehetősége.

E-mail-es megerősítés letiltása

Bár még nem tanultuk az e-mail küldés folyamatát és lehetőségeit, de a regisztráció során a Laravel Breeze lehetővé teszi nekünk, hogy e-mail-es megerősítést küldjünk és várjunk el a felhasználóinktól. Ezt a funkcionalitást is hasonlóan tudjuk letiltani, mint ahogy már a regisztrációnál tettük az imént.

Route::get('verify-email', [EmailVerificationPromptController::class, '__invoke'])
                ->name('verification.notice');

Route::get('verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke'])
                ->middleware(['signed', 'throttle:6,1'])
                ->name('verification.verify');

Az itteni útvonalakban van egy érdekes, eddig nem használt middleware-ünk, ami a throttle névre hallgat és utána van két szám is. Ennek a segítségével tudjuk tudjuk meghatározni (jelen esetben a bejelentkezett felhasználónak), hogy x darab alkalommal próbálkozhat ezzel a tevékenységgel y percen belül. Jelen esetben a második útvonal middleware-je szerint 6-szor próbálkozhat az e-mail-es megerősítéssel 1 percen belül.

Jelszóemlékeztető küldésének letiltása

Hasonlóan, mint az előzőeknél, ezt az útvonalat kell kikommentelni:

Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
                ->name('password.request');

Megjegyzés: ezeknél az útvonalaknál (a regisztrációnál, a megerősítésnél és a jelszó emlékeztető tiltásánál is) elég a "get"-es útvonalakat letiltani, mivel a "post"-osok védve vannak, nem elérhetőek alapból, illetve token-es védelemmel vannak ellátva, így azokat nem muszáj letiltanunk ahhoz, hogy a regisztrációt vagy az e-mail-es érvényesítést letiltsuk.

Regisztráció utáni automatikus bejelentkeztetés letiltása

Ahhoz, hogy ezt tesztelni tudjuk, persze érdemes előtte újra engedélyezni a regisztráció lehetőségét, különben nem fogunk tudni regisztrálni. Ennek a funkciónak az eléréséhez RegisteredUserController store(...) metódusában kell kikommentelni a következő sort:

Auth::login($user);

Ekkor nem fog minket bejelentkeztetni az alkalmazás, hanem regisztráció után a bejelentkezési űrlapot fogjuk megkapni.


2. Átirányítások

Regisztráció utáni átirányítás

Ha már az automatikus bejelentkeztetés tiltása után úgyis ott vagyunk a RegisteredUserController store metódusánál, akkor ugyanitt a metódus végén van egy átirányítás:

return redirect(RouteServiceProvider::HOME);

Ez az app / Providers / RouteServiceProvider osztályban lévő HOME konstans szerint a "/dashboard" útvonalra irányít át minket, amit mi szabadon megváltoztathatnánk, mondjuk ha ezt átírjuk a "/"-ra, akkor a regisztráció végrehajtódna és utána bejönne a főoldalunk.

Kiegészítés és magyarázat az előzőekhez: amikor letiltottuk a regisztráció utáni automatikus bejelentkeztetést, attól a store metódus végén még ugyanúgy erre a "/dashboard" útvonalra irányított minket a rendszer, de mivel az a web.php-ban látható módon ez az útvonal "auth" és "verified" middleware-rel védett, tehát csak bejelentkezett felhasználók érhetik el, emiatt jutottunk ott el a bejelentkezési útvonalra és űrlapra.

Bejelentkezés utáni átirányítás

Az átirányítás a bejelentkezés után ugyanoda történik ("/dashboard"), mint amikor a regisztráció után automatikusan bejelentkeztettük a felhasználót. Ha ezt a HOME konstanst megváltoztatjuk, akkor a bejelentkeztetés után máshova is irányíthatjuk, például a főoldalra ("/"). Ha azt szeretnénk, hogy a regisztráció után máshova irányítsuk a felhasználót, tehát ne legyen így "összekötve" a bejelentkezés utáni átirányítással, akkor az előbbi "return" utasítás redirect(...) metódusának kell más paramétert beállítanunk, mert most mindkét folyamat az ide (/dashboard) való átirányítással zárul.

De visszatérve a bejelentkezésre, ha a bejelentkezés utáni átirányítást szeretnénk felülírni, akkor az app / Http / Controllers / Auth / AuthenticatedSessionController store() metódusát kell módosítanunk és az ottani return utasítás elé kell beszúrnunk egy olyan sort, ami máshova irányítja a frissen bejelentkezett felhasználót (vagy az itten redirect() paraméterét kell módosítanunk.

Több lehetőségünk is van tehát, válasszunk egy nekünk szimpatikusat, vagy hagyjuk meg az alapértelmezett folyamatot olyannak amilyen.


3. Regisztrációs és bejelentkezési folyamat bővítése, módosítása

Regisztráció átalakítása: 1. lépés: a migrációs fájl

Adjuk hozzá a felhasználónevet a regisztrációs űrlaphoz. Ehhez először kell egy username nevű mező a users táblában, hozzuk létre az ehhez szükséges migrációs fájlt:

php artisan make:migration add_username_to_users_table

Az up() metódusba írjuk ezt:

$table->string('username')->after('name')->unique();

A name mező után szeretném beszúrni, de ez csak MySQL-ben működne, SQLite-ban sajnos az utolsó helyre szúrja be mindig az új mezőt.

A down() metódusba írjuk ezt:

$table->dropColumn('username');

Ha most szeretném futtatni a php artisan migrate parancsot, akkor hibát kapnék, mivel már két felhasználóm is van az adatbázisban, emiatt egy kötelezően egyedi értékeket tartalmazó oszlop nem adható hozzá alapértelmezett érték nélkül. Sajnos az sem segítene, ha adnék hozzá default értéket, mivel a két felhasználómnak (soromnak) ugyanazt az értéket nem engedné hozzáadni. Így inkább egy friss adatbázisszerkezetet hozok létre, üres users táblával:

php artisan migrate:fresh

Így már működni fog, létrejön a táblában a username mező, de üres lett a users tábla.

Regisztráció átalakítása: 2. lépés: a model fájl

Nyissuk meg az app / Models / User.php fájlt és adjuk hozzá az új mezőt a $fillable tömbünkhöz.

protected $fillable = [
  'name',
  'email',
  'password',
  'username',
];

Regisztráció átalakítása: 3. lépés: a regisztrációs nézet fájl

Nyissuk meg a resources / views / auth / register.blade.php -t. Itt ugye a regisztráció során látható űrlap kódját láthatjuk. Talán a legegyszerűbb, ha lemásoljuk a "Name" szekciót és kicsit módosítottan utána beszúrjuk ugyanúgy:

<!-- Username -->
<div>
  <x-input-label for="username" :value="__('Username')" />
  
  <x-text-input id="username" class="block mt-1 w-full" type="text" name="username" :value="old('username')" required autofocus />
  
  <x-input-error :messages="$errors->get('username')" class="mt-2" />
</div>

Így a label, input és error részek is megfelelőek lesznek. A regisztrációs űrlapon pedig meg is jelenik az új mező (bekarikázva kiemeltem):

Regisztráció átalakítása: 4. lépés: a felhasználó létrehozása bővített adatokkal

Térjünk vissza RegisteredUserController.php store() metódusára, itt pedig bővítsük ki felhasználó létrehozásához tartozó kódot:

$user = User::create([
  'name' => $request->name,
  'username' => $request->username,
  'email' => $request->email,
  'password' => Hash::make($request->password),
]);

Regisztráció átalakítása: Teszteljük le!

Menjünk rá a regisztrációs űrlapra a böngészőben és regisztráljunk be egy felhasználót (nyugodtan lehet olyat, akit már korábban regisztráltunk, mivel úgyis törlődött az adatbázistáblánk itt az 1. lépésben).

Az űrlap kitöltése után ismét megkapom a bejelentkezési űrlapot, mivel nálam a "/dashboard" útvonalra akar navigálni, amihez bejelentkezés szükséges. A bejelentkezés után pedig látom, hogy sikeres is volt a regisztráció (az adatbázisban a users táblában látható, hogy a felhasználónevet is hozzáadta, amit a regisztrációkor megadtam neki).

Bejelentkezés átalakítása: 1. lépés: a bejelentkezési űrlap módosítása

Valósítsuk meg a bejelentkezést úgy, hogy ne e-mail címet kérjen, hanem az új username mező alapján történjen meg az azonosítás.

A resources / views / auth / login.blade.php -ból vegyük ki az e-mail-es szekciót és illesszük be a username-es részt, amit már egyszer bemásoltunk a regisztrációs űrlapba is. Itt látható az eredménye:

Bejelentkezés átalakítása: 2. lépés: a bejelentkezési logika módosítása

Adódik a felvetés, hogy ennek a megoldását az AuthenticatedSessionController-ben kell keresnünk és nem is tévedünk ezzel akkorát. Az itteni store() metódus magjában az első lényegi sor, ami ez:

$request->authenticate();

Ez végzi el a kérés hitelesítését. Viszont jó lenne, ha egy picit többet látnánk az authenticate() metódus magjából, úgyhogy keressük meg a forrását: ez az app / Http / Requests / Auth / LoginRequest.php fájlban található meg.

Ennek a fájlnak a logikáját több helyen is módosítanunk kell. Kezdjük a rules() metódussal: töröljük ki belőle az email-re vonatkozó sort és vegyük fel a username-est:

return [
  'username' => ['required', 'string'],
  'password' => ['required', 'string'],
];

A felhasználónévre csak az a validációs szabályunk, hogy kötelező mező és szöveges tartalomnak kell lennie benne. Következik az authenticate() metódus, amelyben az "email" szövegeket "username"-re cserélhetjük, ennél többet, most nem kell tudnunk erről:

public function authenticate()
{
  $this->ensureIsNotRateLimited();
  
  if (! Auth::attempt($this->only('username', 'password'), $this->boolean('remember'))) {
    RateLimiter::hit($this->throttleKey());
    
    throw ValidationException::withMessages([
      'username' => trans('auth.failed'),
    ]);
  }
  
  RateLimiter::clear($this->throttleKey());
}

A további két metódusunkban (ensureIsNotRateLimited és throttleKey) is csak cseréljük ki az "email"-t "username"-re és készen is leszünk.

Ha most teszteljük, akkor már működik is a bejelentkezés az imént regisztrált felhasználónk felhasználónév és jelszó párosával.


4. Validáció

Ezt a folyamatot megint kettébonthatjuk regisztrációs és bejelentkezési validációra. Kezdjük a regisztrációssal, itt szükségünk van a RegisteredUserController-re, azon belül pedig a store() metódusra kell koncentrálnunk:

$request->validate([
  'name' => ['required', 'string', 'max:255'],
  'username' => ['required', 'string', 'max:255', 'unique:users'],
  'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
  'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);

Hozzáadtam a kérés validálásához a username-re vonatkozó szabályaimat: kötelező mező legyen, szöveges tartalmú, maximum 255 karakter hosszú és elvárás vele szemben, hogy egyedi érték legyen a tábla ezen mezőjében az új felhasználónév.

Ehhez hasonlóan a bejelentkezési folyamat új, felhasználónév mezőjét is tudjuk validálni a LoginRequest-ben (imént szerkesztettük már ezt):

return [
  'username' => ['required', 'string', 'max:255'],
  'password' => ['required', 'string'],
];

Most annyival bővítettem ezt ki, hogy itt is maximum 255 karakter hosszú felhasználónevet enged megadni a rendszer, teljesen logikusan az előzőek alapján.


5. Hiánypótlás: releváns Blade direktívák bemutatása

Az előző, Laravel Breeze alapokat bemutató bejegyzésemben be szerettem volna mutatni néhány olyan Blade direktívát, amelyek tipikusan a felhasználói hitelesítésnél lehetnek hasznosak, ott ez végül elmaradt, most viszont pótlom ezt a hiányosságot.

Módosítsuk a Dashboard nézetünket úgy (ebben köszöntjük a bejelentkezett felhasználót), hogy a nevén szólítjuk:

Itt az Auth osztály user() metódusán keresztül érjük el a bejelentkezett felhasználó adatait (adatmezőit). Itt az eredménye:

Az Auth osztály használatát az auth() segédfüggvénnyel is helyettesíthetjük. Itt a példában, most tovább fűzöm a sort és az auth() segédfüggvény használatánál a felhasználó e-mail címét kérem le az adatbázisból:

Az eredménye:

Így tudok tehát felhasználó specifikus adatmezőket használni azokban a nézetekben, amelyeknél elvárás, hogy csak bejelentkezés után legyenek elérhetőek.

Előfordulhat viszont, hogy vannak olyan nézet fájljaink, amelyeknél nincs az imént említett elvárás: tehát egyszerű látogatók és bejelentkezett felhasználók is tudják megtekinteni a nézetet, de esetleg a bejelentkezett felhasználóknak plusz információkat is meg szeretnénk jeleníteni az oldalon. Ilyenkor az nem megoldás, amit az imént használtunk, mivel amikor az Auth osztályon vagy auth() metóduson keresztül szeretnénk lekérni a felhasználó adatait, akkor a sima látogatók egy Exception hibát kapnának, mivel nekik null értékkel térnének vissza ezek a lekérések és a null-nak nem lehetne lekérni a "name" és "email" mezőit. Ennek megoldásához egy feltételvizsgálatot kell elvégeznünk a nézetben ahhoz, hogy tudjuk, be van-e jelentkezve az oldalunkra a látogató (tehát bejelentkezett felhasználónk böngészi-e a nézetet). A Blade eszközkészletének direktívái lesznek itt a segítségünkre. Nyissuk meg a welcome nézetünket. Talán egy picit túl sok Tailwind CSS osztály látható, úgyhogy inkább szerkesszük a head-ben lévő title oldalcímet, ami egyelőre csak simán "Laravel".

A title tag-en belüli részt tehát kibővítettük egy if-else elágazással, ami annyit csinál, hogy ellenőrzi, hogy a látogató bejelentkezett felhasználó-e, ha igen, akkor az oldal címében (látható itt: böngésző lapfülén vagy a tálcán lévő böngészőnél) köszöntjük a felhasználót (nem a dashboard, hanem a welcome nézetben vagyunk!), ha pedig nincs bejelentkezve a látogató, akkor csak simán Laravel az oldal címe.

Az @if és @endif páros ebben az esetben, mivel esetleg sokszor kell ellenőriznünk a nézetben, hogy bejelentkezett felhasználóval van-e dolgunk, ezért lecserélhető erre az egyszerűsített formára: @auth és @endauth (az @else marad ugyanúgy). Tehát így:

Egyszerűbb és letisztultabb megoldás ez utóbbi.

De meg is tudjuk fordítani ezt a logikát, hogy ne azt ellenőrizzük, bejelentkezett-e a felhasználó, hanem azt, hogy látogató-e (vendég-e). Ezt így tudjuk megtenni az újabb Blade direktívákkal:

Itt ugye, ha vendég, akkor simán Laravel a weboldal címe, ha pedig nem-vendég, tehát be van jelentkezve a felhasználó, akkor köszöntjük a címben.

További példát találunk ugyanitt a body elején, nézzük csak meg az alábbi kódot:

Láthatunk itt egy feltételvizsgálatot az útvonallal kapcsolatban, ami arra utal, hogy csak akkor jelenítjük meg ezt a szekciót jobb felül az oldalon, ha van regisztrálva login útvonalunk. Innentől kezdve pedig, ha be vagyunk jelentkezve, akkor elérhetjük a dashboard weblinket, ha pedig nem vagyunk bejelentkezve, akkor lehetőségünk van a bejelentkezésre (és ha a regisztráció útvonal regisztrálva van az útvonalak között), akkor lehetőségünk van azt is elérni.


Összegzés

A bejegyzésben áttekintettem számos olyan, sokszor szükséges módosítást, amelyeket a regisztráció / bejelentkezés témakörében érdekes lehet a számunkra. Gyakorlati szempontból végigvizsgáltuk ezeket a tippeket és trükköket, felhívtam közben a figyelmet a buktatókra, érdekességekre is. A bejegyzésben elvégzett módosítások ezen a Github commit linken megtalálhatóak, de nem minden módosítást hagytam érvényben a gyakorlati megvalósítások során, ugyanis akkor nem tudtam volna a később tárgyalt technikákat is bemutatni, ha például meghagyom a bejelentkezés / regisztráció letiltását. Amikor viszont ezeket kivettem, akkor csak kommenteztem őket, tehát a kód megtalálható azon a helyen, ahol a bejegyzés is említi.