Authentification complète de l'utilisateur et contrôle d'accès - Un didacticiel sur le passeport Laravel, Pt. 1
Publié: 2022-03-11Lors du développement d'une application Web, il est généralement judicieux de la diviser en deux niveaux. Une API de niveau intermédiaire interagit avec la base de données, et un niveau Web se compose généralement d'un SPA ou d'un MPA frontal. De cette façon, une application Web est couplée de manière plus lâche, ce qui facilite la gestion et le débogage à long terme.
Lorsque l'API a été créée, la configuration de l'authentification et de l'état dans un contexte d'API sans état peut sembler quelque peu problématique.
Dans cet article, nous verrons comment implémenter l'authentification complète des utilisateurs et une forme simple de contrôle d'accès dans une API utilisant Laravel et Passport. Vous devez avoir une expérience de travail avec Laravel car il ne s'agit pas d'un didacticiel d'introduction.
Prérequis d'installation :
- PHP 7+, MySQL et Apache (les développeurs souhaitant installer les trois à la fois peuvent utiliser XAMPP.)
- Compositeur
- Laravel 7
- Passeport Laravel. Étant donné que les API sont généralement sans état et n'utilisent pas de sessions, nous utilisons généralement des jetons pour conserver l'état entre les requêtes. Laravel utilise la bibliothèque Passport pour implémenter un serveur OAuth2 complet que nous pouvons utiliser pour l'authentification dans notre API.
- Postman, cURL ou Insomnia pour tester l'API - cela dépend de vos préférences personnelles
- Editeur de texte de votre choix
- Aides Laravel (pour Laravel 6.0 et versions ultérieures) - après avoir installé Laravel et Passport, exécutez simplement :
composer require laravel/helpers
Avec ce qui précède installé, nous sommes prêts à commencer. Assurez-vous de configurer votre connexion à la base de données en modifiant le fichier .env
.
Tutoriel Laravel Passport, Étape 1 : Ajouter un contrôleur et un modèle pour les demandes factices
Tout d'abord, nous allons créer un contrôleur et un modèle pour les requêtes factices. Le modèle ne sera pas très utile dans ce tutoriel, c'est juste pour donner une idée des données que le contrôleur est censé manipuler.
Avant de créer le modèle et le contrôleur, nous devons créer une migration. Dans un terminal ou une fenêtre cmd.exe
, si vous utilisez Windows, exécutez :
php artisan make:migration create_articles_table --create=articles
Maintenant, allez dans le dossier database/migrations
et ouvrez le fichier avec un nom similaire à xxxx_xx_xx_xxxxxx_create_articles_table.php
.
Dans la fonction up
de la classe, nous écrirons ceci :
Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->string('body'); $table->integer('user_id'); $table->timestamps(); });
Ensuite, nous allons créer un modèle d' Article
. Pour ce faire, exécutez :
php artisan make:model Article
Nous créons ensuite le contrôleur ArticleController
en exécutant :
php artisan make:controller ArticleController --resource
Ensuite, nous allons éditer le fichier app/Providers/AppServiceProvider.php
et importer la classe Illuminate\Support\Facades\Schema
en ajoutant :
use Illuminate\Support\Facades\Schema
…au bas des importations en haut du fichier.
Ensuite, dans la fonction boot
, nous écrirons :
Schema::defaultStringLength(191);
Après tout cela est fait, nous pouvons exécuter:
php artisan migrate
…pour appliquer la migration que nous avons créée ci-dessus.
Tutoriel Laravel Passport, étape 2: créer les éléments de middleware nécessaires
Ici, nous allons ajouter les morceaux de middleware qui seront nécessaires au fonctionnement de l'API.
Réponses JSON
Le premier élément nécessaire est le middleware ForceJsonResponse
, qui convertira automatiquement toutes les réponses en JSON.
Pour ce faire, exécutez :
php artisan make:middleware ForceJsonResponse
Et voici la fonction handle de ce middleware, dans App/Http/Middleware/ForceJsonReponse.php
:
public function handle($request, Closure $next) { $request->headers->set('Accept', 'application/json'); return $next($request); }
Ensuite, nous allons ajouter le middleware à notre fichier app/Http/Kernel.php
dans le tableau $routeMiddleware
:
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
Ensuite, nous l'ajouterons également au tableau $middleware
dans le même fichier :
\App\Http\Middleware\ForceJsonResponse::class,
Cela garantirait que le middleware ForceJsonResponse
est exécuté à chaque requête.
CORS (Partage de ressources cross-origin)
Pour permettre aux consommateurs de notre API REST Laravel d'y accéder depuis une origine différente, nous devons configurer CORS. Pour ce faire, nous allons créer un middleware appelé Cors
.
Dans un terminal ou une invite de commande, cd
au répertoire racine du projet et exécutez :
php artisan make:middleware Cors
Ensuite, dans app/Http/Middleware/Cors.php
, ajoutez le code suivant :
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'); }
Pour charger ce middleware, nous devrons ajouter une ligne au tableau $routeMiddleware
de app/Http/Kernel.php
:
'cors' => \App\Http\Middleware\Cors::class,
De plus, nous devrons l'ajouter au tableau $middleware
comme nous l'avons fait pour le middleware précédent :
\App\Http\Middleware\Cors::class,
Après cela, nous ajouterons ce groupe de routes à routes/api.php
:
Route::group(['middleware' => ['cors', 'json.response']], function () { // ... });
Toutes nos routes API iront dans cette fonction, comme nous le verrons ci-dessous.
Tutoriel Laravel Passport, Étape 3 : Créer des contrôleurs d'authentification utilisateur pour l'API
Nous voulons maintenant créer le contrôleur d'authentification avec les fonctions de login
et register
.
Tout d'abord, nous allons exécuter :
php artisan make:controller Auth/ApiAuthController
Nous allons maintenant importer certaines classes dans le fichier app/Http/Controllers/Auth/ApiAuthController.php
. Ces classes vont être utilisées dans la création des fonctions login
et register
. Nous allons importer les classes en ajoutant :
use App\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str;
… vers le haut du contrôleur.
Maintenant, pour ajouter l'authentification API Laravel pour nos utilisateurs, nous allons créer les fonctions login
, logout
et register
(signup) dans le même fichier.
La fonction de register
ressemblera à ceci :
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); }
La fonction de login
ressemble à ceci :
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); } }
Et enfin, la fonction 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); }
Après cela, nous devons ajouter les fonctions login
, register
et logout
à nos routes, c'est-à-dire au sein du groupe de routes déjà présent dans l'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'); // ... });
Enfin, nous devons ajouter le trait HasApiToken
au modèle User
. Accédez à app/User
et assurez-vous d'avoir :
use HasApiTokens, Notifiable;
… en tête de classe.
Ce que nous avons jusqu'à présent…
Si nous démarrons le serveur d'application — c'est-à-dire si nous php artisan serve
— puis essayons d'envoyer une requête GET
à la route /api/user
, nous devrions recevoir le message :
{ "message": "Unauthenticated." }
C'est parce que nous ne sommes pas authentifiés pour accéder à cette route. Pour rendre certaines routes de votre choix protégées, nous pouvons les ajouter à routes/api.php
juste après les lignes Route::post
:
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here });
Avant de poursuivre, nous ajouterons la route de déconnexion au middleware auth:api
car Laravel utilise un jeton pour déconnecter l'utilisateur, un jeton auquel il n'est pas possible d'accéder depuis l'extérieur du middleware auth:api
. Nos routes publiques ressemblent à ceci :
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'); // ... });
Nos itinéraires protégés , en revanche, ressemblent à ceci :
Route::middleware('auth:api')->group(function () { // our routes to be protected will go in here Route::post('/logout', 'Auth\ApiAuthController@logout')->name('logout.api'); });
Nous allons maintenant accéder à l' ArticleController
que nous avons créé dans app/Http/Controllers/ArticleController.php
et supprimer les méthodes de create
et d' edit
de cette classe. Après cela, nous ajouterons le morceau de code suivant, légèrement modifié, à chaque fonction restante :
$response = ['message' => '<function name> function']; return response($response, 200);
Nous remplirons <function name>
selon le cas. Par exemple, la fonction update
aura ceci comme corps :
$response = ['message' => 'update function']; return response($response, 200);
Un test manuel d'authentification Laravel : création d'un utilisateur
Pour enregistrer un utilisateur, nous enverrons une requête POST
à /api/register
avec les paramètres suivants : name
, email
(qui doit être unique), password
et password_confirmation
.
Lorsque l'utilisateur est créé, l'API renverra un jeton, que nous utiliserons dans d'autres requêtes comme moyen d'authentification.
Pour vous connecter, nous enverrons une requête POST
à /api/login
. Si nos informations d'identification sont correctes, nous obtiendrons également un jeton de notre API de connexion Laravel de cette manière.
Le jeton d'autorisation que nous recevons de cette demande peut être utilisé lorsque nous voulons accéder à une route protégée. Dans Postman, l'onglet "Autorisation" comporte une liste déroulante dans laquelle le type peut être défini sur "Jeton porteur", après quoi le jeton peut entrer dans le champ du jeton.
Le processus est assez similaire dans Insomnia.
Les utilisateurs de cURL peuvent faire l'équivalent en passant le paramètre -H "Authorization: Bearer <token>"
, où <token>
est le jeton d'autorisation donné à partir de la réponse de connexion ou d'enregistrement.
Comme avec cURL, si les développeurs prévoient d'utiliser l'API à l'aide d'axios ou d'une bibliothèque de ce type, ils peuvent ajouter un en-tête Authorization
avec la valeur Bearer <token>
.
Tutoriel Laravel Passport, Étape 4 : Créer une fonctionnalité de réinitialisation de mot de passe
Maintenant que l'authentification de base est effectuée, il est temps de configurer une fonction de réinitialisation du mot de passe.
Pour ce faire, nous pouvons choisir de créer un répertoire de contrôleur api_auth
, de créer de nouveaux contrôleurs personnalisés et d'implémenter la fonction ; ou nous pouvons modifier les contrôleurs d'authentification que nous pouvons générer avec Laravel. Dans ce cas, nous allons modifier les contrôleurs d'authentification, puisque toute l'application est une API.

Tout d'abord, nous allons générer les contrôleurs d'authentification en exécutant :
composer require laravel/ui php artisan ui vue --auth
Nous allons modifier la classe dans app/Http/Controllers/Auth/ForgotPasswordController.php
, en ajoutant ces deux méthodes :
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); }
Ensuite, nous devons configurer le contrôleur qui réinitialise réellement le mot de passe, nous allons donc naviguer vers app/Http/Controllers/Auth/ResetPasswordController.php
et remplacer les fonctions par défaut comme ceci :
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); }
Nous devons également importer certaines classes dans le contrôleur en ajoutant :
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash;
… vers le haut du contrôleur.
Nous voudrons également modifier la notification par e-mail utilisée, car la notification par e-mail fournie avec Laravel n'utilise pas de jetons API pour l'autorisation. Nous pouvons en créer un nouveau sous app/Notifications
en exécutant cette commande :
php artisan make:notification MailResetPasswordNotification
Nous devrons éditer le fichier app/Notifications/MailResetPasswordNotification.php
pour qu'il ressemble à ceci :
<?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 [ // ]; } }
Pour utiliser cette nouvelle notification, nous devons remplacer la méthode sendPasswordResetNotification
dont User
hérite de la classe Authenticatable
. Tout ce que nous avons à faire est d'ajouter ceci à app/User.php
:
public function sendPasswordResetNotification($token) { $this->notify(new \App\Notifications\MailResetPasswordNotification($token)); }
Avec une configuration de messagerie fonctionnant correctement, les notifications devraient fonctionner à ce stade.
Il ne reste plus qu'à contrôler l'accès des utilisateurs.
Tutoriel Laravel Passport, Étape 5 : Créer un middleware de contrôle d'accès
Avant de créer un middleware de contrôle d'accès, nous devrons mettre à jour la table des user
pour avoir une colonne nommée type
, qui sera utilisée pour déterminer le niveau d'utilisateur : le type 0 est un utilisateur normal, le type 1 est un administrateur et le type 2 est un super-administrateur.
Pour mettre à jour la table des user
, nous devons créer une migration en exécutant ceci :
php artisan make:migration update_users_table_to_include_type --table=users
Dans le fichier nouvellement créé du formulaire database/migrations/[timestamp]_update_users_table.php
, nous devrons mettre à jour les fonctions up
et down
pour ajouter et supprimer la colonne type
, respectivement :
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'); }); }
Ensuite, nous allons lancer php artisan migrate
. Une fois cela fait, nous devons éditer notre fonction de register
dans le fichier ApiAuthController.php
, en l'ajoutant juste avant la ligne avec $user = User::create($request->toArray());
:
$request['type'] = $request['type'] ? $request['type'] : 0;
De plus, nous devrons ajouter cette ligne au tableau $validator
:
'type' => 'integer',
La première de ces deux modifications fera de tous les utilisateurs enregistrés des « utilisateurs normaux » par défaut, c'est-à-dire si aucun type d'utilisateur n'est saisi.
Le middleware de contrôle d'accès lui-même
Nous sommes maintenant en mesure de créer deux middlewares à utiliser pour le contrôle d'accès : un pour les administrateurs et un pour les super-administrateurs.
Nous allons donc exécuter :
php artisan make:middleware AdminAuth php artisan make:middleware SuperAdminAuth
Tout d'abord, nous allons naviguer vers app/Http/Middleware/AdminAuth.php
et importer Illuminate\Support\Facades\Auth
, puis modifier la fonction handle
comme ceci :
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); } }
Nous devrons également modifier la fonction handle
dans 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); } }
Vous devez également importer la classe Auth
en haut des deux fichiers en ajoutant :
use Illuminate\Support\Facades\Auth;
…jusqu'au fond des importations qui s'y trouvent.
Afin d'utiliser notre nouveau middleware, nous référencerons les deux classes dans le noyau, c'est-à-dire dans app/Http/Kernel.php
, en ajoutant les lignes suivantes au tableau $routeMiddleware
:
'api.admin' => \App\Http\Middleware\AdminAuth::class, 'api.superAdmin' => \App\Http\Middleware\SuperAdminAuth::class,
Si les développeurs veulent utiliser le middleware dans une route donnée, tout ce que vous avez à faire est de l'ajouter à la fonction route comme ceci :
Route::post('route','Controller@method')->middleware('<middleware-name-here>');
<middleware-name-here>
dans ce cas peut être api.admin
, api.superAdmin
, etc., selon le cas.
C'est tout ce dont nous avons besoin pour créer notre middleware.
Mettre tous ensemble
Afin de tester que notre authentification et notre contrôle d'accès fonctionnent, il y a quelques étapes supplémentaires à suivre.
Test de l'authentification et du contrôle d'accès Laravel : étape 1
Nous devons modifier la fonction d' index
de ArticleController
et enregistrer la route. (Dans des projets réels, nous utiliserions PHPUnit et le ferions dans le cadre d'un test automatisé. Ici, nous ajoutons manuellement une route à des fins de test - elle peut être supprimée par la suite.)
Nous allons accéder au contrôleur ArticleController
dans app/Http/Controllers/ArticleController
et modifier la fonction d' index
pour qu'elle ressemble à ceci :
public function index() { $response = ['message' => 'article index']; return response($response, 200); }
Ensuite, nous enregistrerons la fonction dans une route en accédant au fichier routes/api.php
et en ajoutant ceci :
Route::middleware('auth:api')->group(function () { Route::get('/articles', 'ArticleController@index')->name('articles'); });
Test de l'authentification et du contrôle d'accès Laravel : étape 2
Nous pouvons maintenant essayer d'accéder à la route sans jeton d'authentification. Nous devrions recevoir une erreur d'authentification.
Test de l'authentification et du contrôle d'accès Laravel : étape 3
Nous pouvons également essayer d'accéder au même itinéraire avec un jeton d'autorisation (celui que nous avons obtenu en nous inscrivant ou en nous connectant plus tôt dans cet article).
Parfois, cela peut provoquer une erreur semblable à celle-ci :
Unknown column 'api_token' in 'where clause' (SQL: select * from `users` where `api_token` = ...
Si cela se produit, les développeurs doivent s'assurer d'avoir exécuté une migration Passport et d'avoir défini ['guards']['api']['driver']
sur passport
dans config/auth.php
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
Après cela, le cache de configuration doit également être mis à jour.
Une fois cela réglé, nous devrions avoir accès à la route.
Test de l'authentification et du contrôle d'accès Laravel : étape 4
Il est temps de tester le contrôle d'accès. Ajoutons ->middleware('api.admin')
à la route des articles, il ressemble donc à ceci :
Route::get('/articles', 'ArticleController@index')->middleware('api.admin')->name('articles');
Nous avons fait en sorte qu'un utilisateur nouvellement créé se voit automatiquement attribuer le type 0, comme nous pouvons le voir via la route api/user
.
Pour cette raison, nous devrions obtenir une erreur en essayant d'accéder au point de terminaison des articles
en tant qu'utilisateur.
À des fins de test, modifions l'utilisateur dans la base de données pour qu'il ait le type
1. Après avoir vérifié à nouveau ce changement via la route api/user
, nous sommes prêts à GET
d'obtenir la route /articles/
.
Cela fonctionne parfaitement.
Les développeurs qui créent des applications plus complexes doivent noter que les contrôles d'accès appropriés ne seront pas aussi simples. Dans ce cas, d'autres applications tierces ou les portes et politiques de Laravel peuvent être utilisées pour implémenter un contrôle d'accès utilisateur personnalisé. Dans la deuxième partie de cette série, nous examinerons des solutions de contrôle d'accès plus robustes et flexibles.
Authentification API Laravel : ce que nous avons appris
Dans ce tutoriel Laravel Passport, nous avons discuté :
- Création d'un contrôleur et d'un modèle factices pour avoir quelque chose à utiliser lors du test de notre exemple Laravel Passport.
- Créer le middleware nécessaire au bon fonctionnement de notre API, traiter CORS et forcer l'API à toujours renvoyer des réponses JSON.
- Configuration de l'authentification de base de l'API Laravel : enregistrement, connexion et déconnexion.
- Configuration de la fonctionnalité de « réinitialisation du mot de passe » basée sur la valeur par défaut de Laravel.
- Création d'un middleware de contrôle d'accès pour ajouter des niveaux d'autorisation d'autorisation utilisateur à différentes routes.
Ce sont des compétences essentielles pour toute personne travaillant dans le domaine des services de développement Laravel. Les lecteurs trouveront le résultat final dans ce dépôt GitHub et devraient maintenant être bien placés pour mettre en œuvre l'authentification avec Laravel. Nous attendons avec impatience les commentaires ci-dessous.