Autentificare completă a utilizatorului și control al accesului – Un tutorial Laravel Passport, Pt. 1

Publicat: 2022-03-11

Când dezvoltați o aplicație web, este, în general, o idee bună să o împărțiți în două niveluri. Un API de nivel mediu interacționează cu baza de date, iar un nivel web constă de obicei dintr-un SPA sau MPA front-end. În acest fel, o aplicație web este cuplată mai lejer, ceea ce face mai ușor de gestionat și de depanat pe termen lung.

Când API-ul a fost creat, configurarea autentificării și a stării într-un context API fără stat poate părea oarecum problematică.

În acest articol, vom analiza cum să implementăm autentificarea completă a utilizatorului și o formă simplă de control al accesului într-un API folosind Laravel și Passport. Ar trebui să aveți experiență de lucru cu Laravel, deoarece acesta nu este un tutorial introductiv.

Cerințe preliminare de instalare:

  • PHP 7+, MySQL și Apache (dezvoltatorii care doresc să le instaleze pe toate trei simultan pot folosi XAMPP.)
  • Compozitor
  • Laravel 7
  • Pașaportul Laravel. Deoarece API-urile sunt, în general, apatride și nu folosesc sesiuni, în general folosim jetoane pentru a păstra starea între solicitări. Laravel folosește biblioteca Passport pentru a implementa un server OAuth2 complet pe care îl putem folosi pentru autentificare în API-ul nostru.
  • Postman, cURL sau Insomnia pentru a testa API-ul - aceasta depinde de preferințele personale
  • Editor de text la alegere
  • Ajutoare Laravel (pentru Laravel 6.0 și versiuni ulterioare) — după ce instalați Laravel și Passport, rulați:
 composer require laravel/helpers

Cu cele de mai sus instalate, suntem gata să începem. Asigurați-vă că ați configurat conexiunea la baza de date prin editarea fișierului .env .

Tutorial Laravel Passport, Pasul 1: Adăugați un controler și un model pentru cererile false

În primul rând, vom crea un controler și un model pentru solicitările inactiv. Modelul nu va fi de mare folos în acest tutorial, este doar pentru a da o idee despre datele pe care controlorul este menit să le manipuleze.

Înainte de a crea modelul și controlerul, trebuie să creăm o migrare. Într-o fereastră de terminal sau cmd.exe , dacă utilizați Windows, rulați:

 php artisan make:migration create_articles_table --create=articles

Acum, accesați folderul de bază de database/migrations și deschideți fișierul cu un nume similar cu xxxx_xx_xx_xxxxxx_create_articles_table.php .

În funcția de up a clasei, vom scrie asta:

 Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });

În continuare, vom crea un model de Article . Pentru a face asta, rulați:

 php artisan make:model Article

Apoi creăm controlerul ArticleController rulând:

 php artisan make:controller ArticleController --resource

În continuare, vom edita fișierul app/Providers/AppServiceProvider.php și vom importa clasa Illuminate\Support\Facades\Schema adăugând:

 use Illuminate\Support\Facades\Schema

…în partea de jos a importurilor din partea de sus a fișierului.

Apoi, în funcția de boot , vom scrie:

 Schema::defaultStringLength(191);

După ce toate acestea sunt făcute, putem rula:

 php artisan migrate

… pentru a aplica migrarea pe care am creat-o mai sus.

Tutorial Laravel Passport, Pasul 2: Creați bucățile necesare de Middleware

Aici, vom adăuga componentele middleware care vor fi necesare pentru ca API-ul să funcționeze.

Răspunsuri JSON

Prima piesă necesară este middleware-ul ForceJsonResponse , care va converti automat toate răspunsurile în JSON.

Pentru a face acest lucru, rulați:

 php artisan make:middleware ForceJsonResponse

Și aceasta este funcția de mâner a acelui middleware, în App/Http/Middleware/ForceJsonReponse.php :

 public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }

Apoi, vom adăuga middleware-ul în fișierul nostru app/Http/Kernel.php în $routeMiddleware :

 'json.response' => \App\Http\Middleware\ForceJsonResponse::class,

Apoi, îl vom adăuga și la matricea $middleware în același fișier:

 \App\Http\Middleware\ForceJsonResponse::class,

Acest lucru ar asigura că middleware-ul ForceJsonResponse este rulat la fiecare solicitare.

CORS (Partajare de resurse între origini)

Pentru a permite consumatorilor API-ului nostru Laravel REST să îl acceseze dintr-o altă origine, trebuie să setăm CORS. Pentru a face asta, vom crea o bucată de middleware numită Cors .

Într-un terminal sau prompt de comandă, cd în directorul rădăcină al proiectului și rulați:

 php artisan make:middleware Cors

Apoi, în app/Http/Middleware/Cors.php , adăugați următorul cod:

 public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', '*') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization'); }

Pentru a încărca această bucată de middleware, va trebui să adăugăm o linie la matricea $routeMiddleware a app/Http/Kernel.php :

 'cors' => \App\Http\Middleware\Cors::class,

De asemenea, va trebui să-l adăugăm la matricea $middleware așa cum am făcut-o pentru middleware-ul anterior:

 \App\Http\Middleware\Cors::class,

După ce facem asta, vom atașa acest grup de rute la routes/api.php :

 Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });

Toate rutele noastre API vor intra în această funcție, așa cum vom vedea mai jos.

Tutorial Laravel Passport, Pasul 3: Creați controlere de autentificare a utilizatorilor pentru API

Acum dorim să creăm controlerul de autentificare cu funcții de login și register .

Mai întâi, vom rula:

 php artisan make:controller Auth/ApiAuthController

Acum vom importa câteva clase în fișierul app/Http/Controllers/Auth/ApiAuthController.php . Aceste clase vor fi folosite la crearea funcțiilor de login și register . Vom importa clasele adăugând:

 use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;

… în partea de sus a controlerului.

Acum, pentru a adăuga autentificarea API Laravel pentru utilizatorii noștri, vom crea funcții de login , logout și register (înregistrare) în același fișier.

Funcția de register va arăta astfel:

 public function register (Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $request['password']=Hash::make($request['password']); $request['remember_token'] = Str::random(10); $user = User::create($request->toArray()); $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); }

Funcția de login este astfel:

 public function login (Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|string|email|max:255', 'password' => 'required|string|min:6|confirmed', ]); if ($validator->fails()) { return response(['errors'=>$validator->errors()->all()], 422); } $user = User::where('email', $request->email)->first(); if ($user) { if (Hash::check($request->password, $user->password)) { $token = $user->createToken('Laravel Password Grant Client')->accessToken; $response = ['token' => $token]; return response($response, 200); } else { $response = ["message" => "Password mismatch"]; return response($response, 422); } } else { $response = ["message" =>'User does not exist']; return response($response, 422); } }

Și, în sfârșit, funcția de logout :

 public function logout (Request $request) { $token = $request->user()->token(); $token->revoke(); $response = ['message' => 'You have been successfully logged out!']; return response($response, 200); }

După aceasta, trebuie să adăugăm funcțiile de login , register și logout la rutele noastre, adică în cadrul grupului de rute deja în API:

 Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register','Auth\ApiAuthController@register')->name('register.api'); Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); // ... });

În cele din urmă, trebuie să adăugăm trăsătura HasApiToken la modelul User . Navigați la app/User și asigurați-vă că aveți:

 use HasApiTokens, Notifiable;

… în fruntea clasei.

Ce avem până acum...

Dacă pornim serverul de aplicații, adică rulăm php artisan serve și apoi încercăm să trimitem o solicitare GET către ruta /api/user , ar trebui să primim mesajul:

 { "message": "Unauthenticated." }

Acest lucru se datorează faptului că nu suntem autentificați pentru a accesa acea rută. Pentru a proteja unele rute alese de dvs., le putem adăuga la routes/api.php imediat după liniile Route::post :

 Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });

Înainte de a trece mai departe, vom adăuga ruta de deconectare la middleware-ul auth:api , deoarece Laravel folosește un token pentru a deconecta utilizatorul - un simbol care nu poate fi accesat din afara middleware-ului auth:api . Rutele noastre publice arată astfel:

 Route::group(['middleware' => ['cors', 'json.response']], function () { // ... // public routes Route::post('/login', 'Auth\ApiAuthController@login')->name('login.api'); Route::post('/register', 'Auth\ApiAuthController@register')->name('register.api'); // ... });

Rutele noastre protejate , pe de altă parte, arată astfel:

 Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });

Acum vom naviga la ArticleController pe care l-am creat în app/Http/Controllers/ArticleController.php și vom șterge metodele de create și edit din acea clasă. După aceea, vom adăuga următoarea bucată de cod, ușor editată, la fiecare funcție rămasă:

 $response = ['message' => '<function name> function']; return response($response, 200);

Vom completa <function name> după caz. De exemplu, funcția de update va avea ca corp acesta:

 $response = ['message' => 'update function']; return response($response, 200);

Un test manual de autentificare Laravel: crearea unui utilizator

Pentru a înregistra un utilizator, vom trimite o solicitare POST către /api/register cu următorii parametri: name , email (care trebuie să fie unic), password și password_confirmation .

O captură de ecran cu trimiterea unei solicitări POST către /api/register folosind Postman.

Când utilizatorul este creat, API-ul va returna un token, pe care îl vom folosi în cereri ulterioare ca mijloc de autentificare.

Pentru a vă conecta, vom trimite o solicitare POST către /api/login . Dacă acreditările noastre sunt corecte, vom primi și un token de la API-ul nostru de conectare Laravel în acest fel.

O captură de ecran cu trimiterea unei solicitări POST către /api/login folosind Postman.

Jetonul de autorizare pe care îl primim din această solicitare îl putem folosi atunci când dorim să accesăm o rută protejată. În Postman, fila „Autorizare” are un meniu drop-down unde tipul poate fi setat la „Jeton de purtător”, după care jetonul poate intra în câmpul de simbol.

Procesul este destul de similar în insomnie.

Utilizatorii cURL pot face echivalentul prin transmiterea parametrului -H "Authorization: Bearer <token>" , unde <token> este simbolul de autorizare dat din răspunsul de conectare sau de înregistrare.

Ca și în cazul cURL, dacă dezvoltatorii intenționează să consume API-ul folosind axios sau o bibliotecă de acest fel, ei pot adăuga un antet de Authorization cu valoarea Bearer <token> .

Tutorial Laravel Passport, Pasul 4: Creați funcționalitatea de resetare a parolei

Acum că autentificarea de bază este finalizată, este timpul să configurați o funcție de resetare a parolei.

Pentru a face acest lucru, putem alege să creăm un director de controler api_auth , să creăm noi controlere personalizate și să implementăm funcția; sau putem edita controlerele de autentificare pe care le putem genera cu Laravel. În acest caz, vom edita controlerele de autentificare, deoarece întreaga aplicație este un API.

În primul rând, vom genera controlerele de autentificare rulând:

 composer require laravel/ui php artisan ui vue --auth

Vom edita clasa în app/Http/Controllers/Auth/ForgotPasswordController.php , adăugând aceste două metode:

 protected function sendResetLinkResponse(Request $request, $response) { $response = ['message' => "Password reset email sent"]; return response($response, 200); } protected function sendResetLinkFailedResponse(Request $request, $response) { $response = "Email could not be sent to this email address"; return response($response, 500); }

Apoi, trebuie să setăm controlerul care resetează parola, așa că vom naviga la app/Http/Controllers/Auth/ResetPasswordController.php și vom suprascrie funcțiile implicite astfel:

 protected function resetPassword($user, $password) { $user->password = Hash::make($password); $user->save(); event(new PasswordReset($user)); } protected function sendResetResponse(Request $request, $response) { $response = ['message' => "Password reset successful"]; return response($response, 200); } protected function sendResetFailedResponse(Request $request, $response) { $response = "Token Invalid"; return response($response, 401); }

De asemenea, trebuie să importam câteva clase în controler adăugând:

 use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;

… în partea de sus a controlerului.

Vom dori să modificăm și ce notificare prin e-mail este folosită, deoarece notificarea prin e-mail care vine cu Laravel nu folosește token-uri API pentru autorizare. Putem crea unul nou sub app/Notifications rulând această comandă:

 php artisan make:notification MailResetPasswordNotification

Va trebui să edităm fișierul app/Notifications/MailResetPasswordNotification.php pentru a arăta astfel:

 <?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Auth\Notifications\ResetPassword; use Illuminate\Support\Facades\Lang; class MailResetPasswordNotification extends ResetPassword { use Queueable; protected $pageUrl; public $token; /** * Create a new notification instance. * * @param $token */ public function __construct($token) { parent::__construct($token); $this->pageUrl = 'localhost:8080'; // we can set whatever we want here, or use .env to set environmental variables } /** * Get the notification's delivery channels. * * @param mixed $notifiable * @return array */ public function via($notifiable) { return ['mail']; } /** * Get the mail representation of the notification. * * @param mixed $notifiable * @return \Illuminate\Notifications\Messages\MailMessage */ public function toMail($notifiable) { if (static::$toMailCallback) { return call_user_func(static::$toMailCallback, $notifiable, $this->token); } return (new MailMessage) ->subject(Lang::getFromJson('Reset application Password')) ->line(Lang::getFromJson('You are receiving this email because we received a password reset request for your account.')) ->action(Lang::getFromJson('Reset Password'), $this->pageUrl."?token=".$this->token) ->line(Lang::getFromJson('This password reset link will expire in :count minutes.', ['count' => config('auth.passwords.users.expire')])) ->line(Lang::getFromJson('If you did not request a password reset, no further action is required.')); } /** * Get the array representation of the notification. * * @param mixed $notifiable * @return array */ public function toArray($notifiable) { return [ // ]; } }

Pentru a folosi această nouă notificare, trebuie să suprascriem metoda sendPasswordResetNotification pe care User o moștenește din clasa Authenticatable . Tot ce trebuie să facem este să adăugăm acest lucru la app/User.php :

 public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }

Cu o configurare de e-mail care funcționează corect, notificările ar trebui să funcționeze în acest moment.

Tot ce a mai rămas acum este controlul accesului utilizatorilor.

Tutorial Laravel Passport, Pasul 5: Creați un program intermediar de control al accesului

Înainte de a crea middleware de control al accesului, va trebui să actualizăm tabelul de user pentru a avea o coloană numită type , care va fi folosită pentru a determina nivelul de utilizator: tipul 0 este un utilizator normal, tipul 1 este un administrator și tipul 2 este un super-administrator.

Pentru a actualiza tabelul de user , trebuie să creăm o migrare prin rularea următoarei:

 php artisan make:migration update_users_table_to_include_type --table=users

În fișierul nou creat din formularul database/migrations/[timestamp]_update_users_table.php , va trebui să actualizăm funcțiile up și down pentru a adăuga și, respectiv, a elimina coloana de type :

 public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('type'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropIfExists('type'); }); }

În continuare, vom rula php artisan migrate . Odată făcut acest lucru, trebuie să ne edităm funcția de register în fișierul ApiAuthController.php , adăugând aceasta chiar înainte de linia cu $user = User::create($request->toArray()); :

 $request['type'] = $request['type'] ? $request['type'] : 0;

De asemenea, va trebui să adăugăm această linie la matricea $validator :

 'type' => 'integer',

Prima dintre aceste două editări va face ca toți utilizatorii înregistrați să fie „utilizatori normali” în mod implicit, adică dacă nu este introdus niciun tip de utilizator.

Middleware-ul de control al accesului în sine

Acum suntem în măsură să creăm două componente middleware pe care să le folosim pentru controlul accesului: una pentru administratori și una pentru super-administratori.

Deci vom alerga:

 php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth

Mai întâi, vom naviga la app/Http/Middleware/AdminAuth.php și vom importa Illuminate\Support\Facades\Auth , apoi vom handle funcția de ghidare astfel:

 public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 1) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }

De asemenea, va trebui să edităm funcția handle în app/Http/Middleware/SuperAdminAuth.php :

 public function handle($request, Closure $next) { if (Auth::guard('api')->check() && $request->user()->type >= 2) { return $next($request); } else { $message = ["message" => "Permission Denied"]; return response($message, 401); } }

De asemenea, ar trebui să importați clasa Auth din partea de sus a ambelor fișiere, adăugând:

 use Illuminate\Support\Facades\Auth;

…la partea de jos a importurilor găsite acolo.

Pentru a folosi noul nostru middleware, vom face referire la ambele clase din nucleu, adică în app/Http/Kernel.php , adăugând următoarele linii la $routeMiddleware :

 'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,

Dacă dezvoltatorii doresc să folosească middleware-ul într-o anumită rută, tot ce trebuie să faceți este să îl adăugați la funcția de rută astfel:

 Route::post('route','Controller@method')->middleware('<middleware-name-here>');

<middleware-name-here> în acest caz poate fi api.admin , api.superAdmin , etc., după caz.

Asta este tot ceea ce este necesar pentru a crea middleware-ul nostru.

Punând totul laolaltă

Pentru a testa dacă autentificarea noastră și controlul accesului funcționează, trebuie să parcurgeți câțiva pași suplimentari.

Testarea autentificării Laravel și a controlului accesului: Pasul 1

Trebuie să modificăm funcția de index al ArticleController și să înregistrăm ruta. (În proiectele din lumea reală, am folosi PHPUnit și am face acest lucru ca parte a unui test automat. Aici, adăugăm manual o rută în scopuri de testare - poate fi eliminată ulterior.)

Vom naviga la controlerul ArticleController la app/Http/Controllers/ArticleController și vom modifica funcția de index pentru a arăta astfel:

 public function index() { $response = ['message' => 'article index']; return response($response, 200); }

În continuare, vom înregistra funcția într-o rută mergând la fișierul routes/api.php și adăugând asta:

 Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });

Testarea autentificării Laravel și a controlului accesului: Pasul 2

Acum putem încerca să accesăm traseul fără un token de autentificare. Ar trebui să primim o eroare de autentificare.

O captură de ecran cu trimiterea unei cereri GET către /api/articles folosind Postman, primind un mesaj „Neautentificat” ca răspuns.

Testarea autentificării Laravel și a controlului accesului: Pasul 3

De asemenea, putem încerca să accesăm același traseu cu un token de autorizare (cel pe care l-am obținut din înregistrare sau logare mai devreme în acest articol).

Uneori, acest lucru poate provoca o eroare similară cu aceasta:

 Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ... 

O captură de ecran a erorii de coloană necunoscută de la OBȚINEREA rutei API/articole folosind Postman.

Dacă se întâmplă acest lucru, dezvoltatorii ar trebui să se asigure că au executat o migrare Passport și că au ['guards']['api']['driver'] setat la passport în config/auth.php :

 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],

După aceea, cache-ul de configurare trebuie, de asemenea, actualizat.

Odată ce este rezolvat, ar trebui să avem acces la traseu.

O captură de ecran cu trimiterea unei solicitări GET către /api/articles folosind Postman, cu un răspuns JSON normal.

Testarea autentificării Laravel și a controlului accesului: Pasul 4

Este timpul să testăm controlul accesului. Să adăugăm ->middleware('api.admin') la ruta articolelor, așa că arată astfel:

 Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');

Am făcut astfel încât unui utilizator nou creat să i se atribuie automat tipul 0, așa cum putem vedea prin ruta api/user .

O captură de ecran cu trimiterea unei cereri GET către /api/user folosind Postman. Răspunsul include un ID, un nume, un e-mail, un marcaj de timp pentru verificarea e-mailului nul, marcajele de timp create și actualizate completate și un tip.

Din acest motiv, ar trebui să primim o eroare la încercarea de a accesa punctul final al articles ca astfel de utilizator.

O captură de ecran cu trimiterea unei solicitări GET către /api/articles folosind Postman, cu un răspuns JSON cu permisiunea refuzată.

În scopul testării, să modificăm utilizatorul din baza de date pentru a avea un type de 1. După ce verificăm din nou această modificare prin intermediul rutei api/user , suntem gata să încercăm din nou să GET ruta /articles/ .

O captură de ecran cu trimiterea unei cereri GET către /api/articles ca utilizator autentificat de Laravel folosind Postman, identică cu o solicitare anterioară, cu excepția faptului că timpul de răspuns al acesteia a fost de 1.280 ms în loc de 890 ms.

Functioneaza perfect.

Dezvoltatorii care realizează aplicații mai complexe ar trebui să rețină că controalele adecvate ale accesului nu vor fi atât de simple. În acest caz, alte aplicații terțe sau porțile și politicile Laravel pot fi utilizate pentru a implementa controlul personalizat al accesului utilizatorilor. În a doua parte a acestei serii, vom analiza soluții mai robuste și mai flexibile de control al accesului.

Autentificare API Laravel: Ce am învățat

În acest tutorial Laravel Passport, am discutat despre:

  1. Crearea unui controler și model fals pentru a avea ceva de folosit în timp ce testăm exemplul nostru Laravel Passport.
  2. Crearea middleware-ului necesar pentru ca API-ul nostru să funcționeze fără probleme, abordând CORS și forțând API-ul să returneze întotdeauna răspunsuri JSON.
  3. Configurarea autentificării de bază Laravel API: înregistrare, conectare și deconectare.
  4. Configurarea funcționalității „resetare parolă” pe baza valorii implicite a lui Laravel.
  5. Crearea unui middleware de control al accesului pentru a adăuga niveluri de autorizare a utilizatorului pe diferite rute.

Acestea sunt abilități esențiale pentru oricine lucrează în domeniul serviciilor de dezvoltare Laravel. Cititorii vor găsi rezultatul final în acest depozit GitHub și ar trebui să fie acum bine poziționați pentru a implementa autentificarea cu Laravel. Așteptăm cu nerăbdare comentariile de mai jos.