Créer une API RESTful sécurisée avec CodeIgniter et des JWT

November 19, 2020
Rédigé par
Oluyemi Olususi
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs
Révisé par
Diane Phan
Twilion

Créer une API RESTful sécurisée avec CodeIgniter et des JSON Web Tokens

L'utilisation et les applications croissantes des services cloud nécessitent un style architectural plus efficace que le protocole SOAP (Simple Object Access Protocol). REST (REpresentational State Transfer) permet une communication légère et sans état entre les clients et l'interface de programmation d'applications (API). La communication étant sans état, le contrôle d'accès des API RESTful est basé sur des tokens qui transportent suffisamment d'informations pour déterminer si le client est autorisé à effectuer l'action requêtée sur la ressource.

Dans ce tutoriel, je vais utiliser CodeIgniter pour créer une API RESTful. CodeIgniter est un puissant framework PHP avec un encombrement très faible qui permet aux développeurs de construire des applications Web complètes.

Conditions préalables

Une compréhension de base de CodeIgniter sera utile dans ce tutoriel. Cependant, je fournirai des explications et des liens vers la documentation officielle tout au long du tutoriel. En cas de doute concernant un concept, vous pouvez consulter le matériel lié avant de poursuivre.

De plus, les éléments suivants doivent être installés sur votre système :

  • Composer. Composer sera utilisé pour la gestion des dépendances dans votre projet CodeIgniter.
  • Une instance de base de données locale. Nous allons utiliser MySQL dans ce tutoriel, mais vous êtes libre de sélectionner votre service de base de données préféré
  • Postman ou une application similaire pour tester nos points de terminaison. Vous pouvez également utiliser cURL à cet effet.

Ce que nous allons construire

Pour démontrer comment créer une API CodeIgniter sécurisée, nous allons créer une API qui sera utilisée pour gérer la base de données client d'une entreprise. Cette base de données contient les données suivantes sur chaque client :

  • Nom
  • Adresse e-mail
  • Montant payé pour retenir les services de l'entreprise (acompte)

L'API obtenue à la fin de ce tutoriel présentera les fonctionnalités suivantes :

  1. Enregistrer un nouvel utilisateur
  2. Authentifier un utilisateur existant
  3. Ajouter un nouveau client
  4. Modifier les détails d'un client existant
  5. Afficher tous les clients
  6. Afficher un seul client par ID
  7. Supprimer un seul client par ID

Les fonctions 3 à 7 seront limitées aux utilisateurs authentifiés.

Mise en route

Créez un nouveau projet CodeIgniter à l'aide de Composer.

composer create-project codeigniter4/appstarter ci-secure-api

Ceci créera un nouveau projet CodeIgniter dans un dossier nommé ci-secure-api. Une fois l'installation terminée, accédez au dossier de projet nouvellement créé à partir du terminal et exécutez l'application sur le serveur de développement local fourni avec CodeIgniter. Pour ce faire, utilisez la commande suivante :

// move into the project
$ cd ci-secure-api

// run the application
$ php spark serve

Accédez à http://localhost:8080/ depuis votre navigateur pour afficher la page d'accueil.

page d'accueil par défaut

Préparation des variables d'environnement

Maintenant que CodeIgniter est installé et en cours d'exécution, l'étape suivante consiste à fournir des variables d'environnement qui seront utilisées par notre application. Arrêtez l'exécution de l'application en appuyant sur les touches CTRL + C du clavier et effectuez une copie du fichier .env nommé .env à l'aide de la commande ci-dessous :

$ cp env .env

CodeIgniter démarre en mode production par défaut. Dans le cadre de ce tutoriel, nous allons le passer en mode développement. Pour ce faire, annuler le commentaire de la ligne ci-dessous et définissez-la sur development :

CI_ENVIRONMENT = development

Ensuite, créez une base de données dans votre environnement local et supprimez le commentaire des variables suivantes pour mettre à jour chaque valeur et établir une connexion réussie à la base de données :

database.default.hostname = localhost
database.default.database = YOUR_DATABASE
database.default.username = YOUR_DATABASE_USERNAME
database.default.password = YOUR_DATABASE_PASSWORD
database.default.DBDriver = MySQLi # this is the driver for a MySQL connection. There are also drivers available for postgres & SQLite3.

Remplacez les espaces réservés YOUR_DATABASEYOUR_DATABASE_USERNAME et YOUR_DATABASE_PASSWORD par vos propres valeurs.

Migrations et seeders

Maintenant que nous avons créé une base de données et configuré une connexion à celle-ci, nous allons créer des migrations pour les tables user et client. Les fichiers de migration sont généralement utiles pour créer une structure de base de données appropriée. Les migrations et les seeders seront créés à l'aide de l'outil CLI CodeIgniter.

Exécutez la commande suivante dans le terminal :

$ php spark migrate:create

L'interface de ligne de commande vous demandera de nommer le fichier de migration, après quoi le fichier de migration sera créé dans le répertoire App/Database/Migrations. Pour ce tutoriel, vous allez créer deux fichiers de migration nommés :

  • add_client
  • add_user

Le nom du fichier de migration sera précédé d'une séquence numérique au format de date AAAA-MM-JJ-HHIISS. Reportez-vous à la documentation CodeIgniter pour obtenir une explication plus détaillée.

Ensuite, mettez à jour le contenu du fichier de migration add_client comme suit :

 

<?php
use CodeIgniter\Database\Migration;

class AddClient extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 5,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false
            ],
            'email' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false,
                'unique' => true
            ],
            'retainer_fee' => [
                'type' => 'INT',
                'constraint' => 100,
                'null' => false,
                'unique' => true
            ],
            'updated_at' => [
                'type' => 'datetime',
                'null' => true,
            ],
        'created_at datetime default current_timestamp',
        ]);
        $this->forge->addPrimaryKey('id');
        $this->forge->createTable('client');
    }

    public function down()
    {
        $this->forge->dropTable('client');
    }
}

Ici, nous avons spécifié les champs et les types de données correspondants pour la table Client.

Ouvrez ensuite le fichier de migration add_user et remplacez son contenu par ce qui suit :

 

<?php

use CodeIgniter\Database\Migration;

class AddUser extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type' => 'INT',
                'constraint' => 5,
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false
            ],
            'email' => [
                'type' => 'VARCHAR',
                'constraint' => '100',
                'null' => false,
                'unique' => true
            ],
            'password' => [
                'type' => 'VARCHAR',
                'constraint' => '255',
                'null' => false,
                'unique' => true
            ],
            'updated_at' => [
                'type' => 'datetime',
                'null' => true,
            ],
            'created_at datetime default current_timestamp',
        ]);
        $this->forge->addPrimaryKey('id');
        $this->forge->createTable('user');
    }

    public function down()
    {
        $this->forge->dropTable('user');
    }
}

Le contenu ci-dessus vous aidera à créer la table user et ses champs. Exécutez maintenant vos migrations à l'aide de la commande ci-dessous :

 $ php spark migrate

Pour faciliter le développement, seedez des données client factices dans votre base de données. L'ensemble factice fzaninotto est une dépendance par défaut dans le squelette CodeIgniter et peut être utilisé pour ajouter des clients aléatoires à la base de données. Tout comme pour la migration, l'outil CLI CodeIgniter sera utilisé pour créer un seeder pour les clients. Exécutez la commande suivante :

 $ php spark make:seeder

L'outil CLI requêtera le nom ClientSeeder. Un fichier ClientSeeder.php sera créé dans le répertoire App/Database/Seeds. Ouvrez le fichier et remplacez son contenu par ce qui suit :

 

<?php

namespace App\Database\Seeds;

use CodeIgniter\Database\Seeder;
use Faker\Factory;

class ClientSeeder extends Seeder
{
    public function run()
    {
        for ($i = 0; $i < 10; $i++) { //to add 10 clients. Change limit as desired
            $this->db->table('client')->insert($this->generateClient());
        }
    }

    private function generateClient(): array
    {
        $faker = Factory::create();
        return [
            'name' => $faker->name(),
            'email' => $faker->email,
            'retainer_fee' => random_int(100000, 100000000)
        ];
    }
}

Peuplez la base de données avec des clients factices à l'aide de la commande suivante :

$ php spark db:seed ClientSeeder

À ce stade, la base de données doit avoir une structure similaire à la capture d'écran ci-dessous :

vue de la base de données

Modèles d'entité

Pour l'interaction de l'API avec la base de données, le modèle de CodeIgniter sera utilisé. Pour que cela fonctionne, deux modèles seront créés : un pour l'utilisateur et un autre pour le client.

Ouvrez le répertoire App/Models et créez les fichiers suivants :  

  • UserModel.php
  • ClientModel.php 

Dans UserModel.php, ajoutez ce qui suit :

 

<?php

namespace App\Models;

use CodeIgniter\Model;
use Exception;

class UserModel extends Model
{
    protected $table = 'user';
    protected $allowedFields = [
        'name',
        'email',
        'password',
    ];
    protected $updatedField = 'updated_at';

    protected $beforeInsert = ['beforeInsert'];
    protected $beforeUpdate = ['beforeUpdate'];

    protected function beforeInsert(array $data): array
    {
        return $this->getUpdatedDataWithHashedPassword($data);
    }

    protected function beforeUpdate(array $data): array
    {
        return $this->getUpdatedDataWithHashedPassword($data);
    }

    private function getUpdatedDataWithHashedPassword(array $data): array
    {
        if (isset($data['data']['password'])) {
            $plaintextPassword = $data['data']['password'];
            $data['data']['password'] = $this->hashPassword($plaintextPassword);
        }
        return $data;
    }

    private function hashPassword(string $plaintextPassword): string
    {
        return password_hash($plaintextPassword, PASSWORD_BCRYPT);
    }
                                      
    public function findUserByEmailAddress(string $emailAddress)
    {
        $user = $this
            ->asArray()
            ->where(['email' => $emailAddress])
            ->first();

        if (!$user) 
            throw new Exception('User does not exist for specified email address');

        return $user;
    }
}

Les fonctions beforeInsert et beforeUpdate vous permettent d'effectuer une opération sur l'entité User avant de l'enregistrer dans la base de données. Dans ce cas, le mot de passe de l'utilisateur est hashé avant d'être enregistré dans la base de données.

Ajoutez le code suivant au fichier ClientModel.php :

 

<?php

namespace App\Models;

use CodeIgniter\Model;
use Exception;

class ClientModel extends Model
{
    protected $table = 'client';
    protected $allowedFields = [
        'name',
        'email',
        'retainer_fee'
    ];
    protected $updatedField = 'updated_at';

    public function findClientById($id)
    {
        $client = $this
            ->asArray()
            ->where(['id' => $id])
            ->first();

        if (!$client) throw new Exception('Could not find client for specified ID');

        return $client;
    }
}

Le champ $table permet au modèle de savoir avec quelle table de base de données il fonctionne principalement. $allowedFields permet au modèle de savoir quelles colonnes de la table peuvent être mises à jour. La fonction findClientById fournit une abstraction propre pour extraire un client de la base de données en fonction de l'idfourni.

Une fois les modèles et la base de données implémentés, les utilisateurs peuvent être ajoutés et authentifiés. Les utilisateurs autorisés peuvent également interagir avec la clientèle actuelle.

Implémentation de JWT

Les Web JSON Web Tokens seront utilisés pour authentifier les utilisateurs et empêcher les utilisateurs non autorisés d'afficher la liste des clients. Pour que cela fonctionne, l'API fournit un token lorsque l'utilisateur s'inscrit ou se connecte correctement. Ce token sera ajouté à l'en-tête des requêtes suivantes pour s'assurer que l'API peut identifier l'utilisateur à l'origine de la requête. Dans ce tutoriel, l'ensemble firebase/php-jwt sera utilisé pour générer les tokens. Exécutez ce qui suit pour l'installer à l'aide de Composer :

$ composer require firebase/php-jwt

Une fois l'installation terminée, ajoutez les éléments suivants à votre fichier .env :

#JWT_SECRET_KEY key is the secret key used by the application to sign JWTS. Pick a stronger one for production.
JWT_SECRET_KEY=kzUf4sxss4AeG5uHkNZAqT1Nyi1zVfpz 
#JWT_TIME_TO_LIVE indicates the validity period of a signed JWT (in milliseconds)
JWT_TIME_TO_LIVE=3600

Ensuite, créez une fonction d'aide pour obtenir la clé secrète dans la classe Services. Accédez à App/Config/Services.php et ajoutez ce qui suit :

public static function getSecretKey(){
    return getenv('JWT_SECRET_KEY');
} 

Création d'un JWT Helper

Pour faciliter la génération et la vérification des tokens, un fichier Helper sera créé. Cela nous permet de séparer les préoccupations dans notre application. Dans le répertoire App/Helpers, créez un fichier nommé jwt_helper.php. Votre fichier devrait ressembler à ceci :

 

<?php

use App\Models\UserModel;
use Config\Services;
use Firebase\JWT\JWT;

function getJWTFromRequest($authenticationHeader): string
{
    if (is_null($authenticationHeader)) { //JWT is absent
        throw new Exception('Missing or invalid JWT in request');
    }
    //JWT is sent from client in the format Bearer XXXXXXXXX
    return explode(' ', $authenticationHeader)[1];
}

function validateJWTFromRequest(string $encodedToken)
{
    $key = Services::getSecretKey();
    $decodedToken = JWT::decode($encodedToken, $key, ['HS256']);
    $userModel = new UserModel();
    $userModel->findUserByEmailAddress($decodedToken->email);
}

function getSignedJWTForUser(string $email)
{
    $issuedAtTime = time();
    $tokenTimeToLive = getenv('JWT_TIME_TO_LIVE');
    $tokenExpiration = $issuedAtTime + $tokenTimeToLive;
    $payload = [
        'email' => $email,
        'iat' => $issuedAtTime,
        'exp' => $tokenExpiration,
    ];

    $jwt = JWT::encode($payload, Services::getSecretKey());
    return $jwt;
}

La fonction getJWTFromRequest vérifie l'en-tête Authorization de la requête entrante et renvoie la valeur du token. Si l'en-tête est manquant, une exception est déclenchée. Celle-ci entraîne à son tour le renvoi d'une réponse HTTP_UNAUTHORIZED (401).

La fonction validateJWTFromRequest utilise le token obtenu au moyen de la fonction getJWTFromRequest. Elle décode ce token afin d'obtenir l'e-mail pour lequel la clé a été générée. Elle tente ensuite de trouver un utilisateur avec cette adresse e-mail dans la base de données. Si l'utilisateur n'a pas été trouvé, le modèle d'utilisateur déclenche une exception qui est détectée et renvoyée à l'utilisateur sous la forme d'une réponse HTTP_UNAUTHORIZED (401).

La fonction getSignedJWTForUser est utilisée pour générer un token pour un utilisateur authentifié. Le JWT codé contient les détails suivants :

  • Adresse e-mail de l'utilisateur authentifié. Elle est utilisée dans les requêtes ultérieures pour valider la source de la requête.
  • Heure à laquelle le token a été généré (iat).
  • Heure d'expiration du token (exp). Elle est obtenue en ajoutant la valeur JWT_TIME_TO_LIVE de notre fichier .env à l'heure actuelle.

Création d'un filtre d'authentification

Dans le répertoire App/Filters, créez un fichier nommé JWTAuthenticationFilter.php. Ce filtre permet de vérifier l'API du JWT avant de transmettre la requête au contrôleur. Si aucun JWT n'est fourni ou si le JWT fourni a expiré, une réponse HTTP_UNAUTHORIZED (401) est renvoyée avec un message d'erreur approprié. Ajoutez ce qui suit à votre fichier :

 

<?php

namespace App\Filters;

use CodeIgniter\API\ResponseTrait;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Config\Services;
use Exception;

class JWTAuthenticationFilter implements FilterInterface
{
    use ResponseTrait;

    public function before(RequestInterface $request, $arguments = null)
    {
        $authenticationHeader = $request->getServer('HTTP_AUTHORIZATION');

        try {

            helper('jwt');
            $encodedToken = getJWTFromRequest($authenticationHeader);
            validateJWTFromRequest($encodedToken);
            return $request;

        } catch (Exception $e) {

            return Services::response()
                ->setJSON(
                    [
                        'error' => $e->getMessage()
                    ]
                )
                ->setStatusCode(ResponseInterface::HTTP_UNAUTHORIZED);

        }
    }

    public function after(RequestInterface $request,
                          ResponseInterface $response,
                          $arguments = null)
    {
    }
}

Comme vous pouvez le voir, JWT Helper est chargé en premier, puis les fonctions getJWTFromRequest et validateJWTFromRequest sont utilisées pour s'assurer que la requête provient d'un utilisateur authentifié avec un token valide.

Enregistrez votre filtre JWTAuthentication et spécifiez le chemin à protéger. Cette opération s'effectue dans le fichier App/Config/Filters.php. Mettez à jour les tableaux $aliases et $filters comme suit :

 

<?php 
namespace Config;

use App\Filters\JWTAuthenticationFilter;
use CodeIgniter\Config\BaseConfig;

class Filters extends BaseConfig
{
    public $aliases = [
        'csrf' => CSRF::class,
        'toolbar' => DebugToolbar::class,
        'honeypot' => \CodeIgniter\Filters\Honeypot::class,
        'auth' => JWTAuthenticationFilter::class // add this line
    ];

    // global filters
    // method filters
    public $filters = [
      'auth' => [
        'before' => [
            'client/*',
            'client'
      ],
    ]
  ];
}

REMARQUE : la barre d'outils de débogage est préchargée par défaut. Il existe des conflits connus, car la barre d'outils de débogage est toujours en construction. Pour la désactiver, ajoutez un commentaire sur l'élément 'toolbar' dans le tableau $globals.

En ajoutant ces éléments, la fonction before dans JWTAuthenticationFilter.php est appelée chaque fois qu'une requête est envoyée à un endpoint commençant par le client. Cela signifie que le contrôleur reçoit/traite la requête uniquement si son en-tête contient un token valide.  

Même si nous n'avons pas de contrôleur, nous pouvons vérifier que notre application fonctionne jusqu'à présent. Ouvrez Postman et faites une requête GET à http://localhost:8080/client. Vous devriez voir quelque chose de similaire à la capture d'écran ci-dessous :

JWT invalide

Ouvrez ensuite le fichier App/Controllers/BaseController.php et ajoutez la fonction suivante :

public function getResponse(array $responseBody,
                            int $code = ResponseInterface::HTTP_OK)
{
    return $this
        ->response
        ->setStatusCode($code)
        ->setJSON($responseBody);
}

Cette fonction sera utilisée par vos contrôleurs pour renvoyer les réponses JSON au client.

REMARQUE : n'oubliez pas d'importer ResponseInterface.

use CodeIgniter\HTTP\ResponseInterface;

BaseController étend le Controller de CodeIgniter, qui fournit des assistants et d'autres fonctions facilitant le traitement des requêtes entrantes. L'une de ces fonctions est validate qui utilise le service de validation de CodeIgniter pour vérifier une requête par rapport aux règles (et aux messages d'erreur si nécessaire) spécifiées dans les fonctions de notre contrôleur. Cette fonction donne de bons résultats avec les requêtes de formulaire (form-data avec Postman). Cependant, elle ne serait pas en mesure de valider les requêtes JSON brutes envoyées à notre API. En effet, le contenu de la requête JSON est stocké dans le champ body tandis que le contenu de la requête form-data est stocké dans le champ post.

Pour contourner ce problème, nous allons écrire une fonction qui vérifie les deux champs dans une requête afin d'obtenir son contenu. Ajoutez ce qui suit à App/Controllers/BaseController.php :

public function getRequestInput(IncomingRequest $request){
    $input = $request->getPost();
    if (empty($input)) {
        //convert request body to associative array
        $input = json_decode($request->getBody(), true);
    }
    return $input;
}

REMARQUE : n'oubliez pas d'importer la classe IncomingRequest.

use CodeIgniter\HTTP\IncomingRequest;

Ensuite, déclarez une fonction qui exécute le service de validation par rapport à $input de notre fonction précédente. Cette fonction est presque la même que la fonction validate intégrée, sauf qu'au lieu d'exécuter la vérification sur IncomingRequest, nous l'exécutons sur l'entrée que nous avons capturée à partir de la fonction getRequestInput.

public function validateRequest($input, array $rules, array $messages =[]){
    $this->validator = Services::Validation()->setRules($rules);
    // If you replace the $rules array with the name of the group
    if (is_string($rules)) {
        $validation = config('Validation');

        // If the rule wasn't found in the \Config\Validation, we
        // should throw an exception so the developer can find it.
        if (!isset($validation->$rules)) {
            throw ValidationException::forRuleNotFound($rules);
        }

        // If no error message is defined, use the error message in the Config\Validation file
        if (!$messages) {
            $errorName = $rules . '_errors';
            $messages = $validation->$errorName ?? [];
        }

        $rules = $validation->$rules;
    }
    return $this->validator->setRules($rules, $messages)->run($input);
}

REMARQUE : n'oubliez pas d'importer les classes nécessaires.

use CodeIgniter\Validation\Exceptions\ValidationException;
use Config\Services;

Une fois cela mis en place, ajoutons la logique pour enregistrer et authentifier les utilisateurs.

Contrôleur d'authentification

Ensuite, créez un fichier nommé Auth.php dans le répertoire App/Controllers. Mettez à jour le fichier comme indiqué ci-dessous :

 

<?php

namespace App\Controllers;

use App\Models\UserModel;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use Exception;
use ReflectionException;

class Auth extends BaseController
{
    /**
     * Register a new user
     * @return Response
     * @throws ReflectionException
     */
    public function register()
    {
        $rules = [
            'name' => 'required',
            'email' => 'required|min_length[6]|max_length[50]|valid_email|is_unique[user.email]',
            'password' => 'required|min_length[8]|max_length[255]'
        ];

 $input = $this->getRequestInput($this->request);
        if (!$this->validateRequest($input, $rules)) {
            return $this
                ->getResponse(
                    $this->validator->getErrors(),
                    ResponseInterface::HTTP_BAD_REQUEST
                );
        }

        $userModel = new UserModel();
       $userModel->save($input);
     

       

return $this
            ->getJWTForUser(
                $input['email'],
                ResponseInterface::HTTP_CREATED
            );

    }

    /**
     * Authenticate Existing User
     * @return Response
     */
    public function login()
    {
        $rules = [
            'email' => 'required|min_length[6]|max_length[50]|valid_email',
            'password' => 'required|min_length[8]|max_length[255]|validateUser[email, password]'
        ];

        $errors = [
            'password' => [
                'validateUser' => 'Invalid login credentials provided'
            ]
        ];

$input = $this->getRequestInput($this->request);


        if (!$this->validateRequest($input, $rules, $errors)) {
            return $this
                ->getResponse(
                    $this->validator->getErrors(),
                    ResponseInterface::HTTP_BAD_REQUEST
                );
        }
       return $this->getJWTForUser($input['email']);

       
    }

    private function getJWTForUser(
        string $emailAddress,
        int $responseCode = ResponseInterface::HTTP_OK
    )
    {
        try {
            $model = new UserModel();
            $user = $model->findUserByEmailAddress($emailAddress);
            unset($user['password']);

            helper('jwt');

            return $this
                ->getResponse(
                    [
                        'message' => 'User authenticated successfully',
                        'user' => $user,
                        'access_token' => getSignedJWTForUser($emailAddress)
                    ]
                );
        } catch (Exception $exception) {
            return $this
                ->getResponse(
                    [
                        'error' => $exception->getMessage(),
                    ],
                    $responseCode
                );
        }
    }
}

Enregistrement

Pour enregistrer un nouvel utilisateur avec succès, les champs suivants sont obligatoires :

  • Un nom.
  • Une adresse e-mail dans un format valide ne comportant pas moins de 8 caractères et pas plus de 255 caractères.
  • Un mot de passe de 8 caractères minimum et de 255 caractères maximum.

La requête entrante est vérifiée par rapport aux règles spécifiées. Les requêtes non valides sont ignorées avec un code (400) HTTP_BAD_REQUEST et un message d'erreur. Si la requête est valide, les données utilisateur sont enregistrées et un token est renvoyé avec les détails enregistrés de l'utilisateur (à l'exception du mot de passe). La réponse HTTP_CREATED (201) informe le client qu'une nouvelle ressource a été créée.

En effectuant une requête POST sur le endpoint du registre (http://localhost:8080/auth/register) avec un nom (name), une adresse e-mail (e-mail) et un mot de passe (password) valides, vous obtenez une réponse similaire à celle présentée ci-dessous :

Register

Authentification

Une authentification réussie nécessite les éléments suivants :

  • Une adresse e-mail dans un format valide ne comportant pas moins de 8 caractères et pas plus de 255 caractères. En outre, l'adresse e-mail doit correspondre à celle d'un utilisateur enregistré.
  • Un mot de passe de 8 caractères minimum et de 255 caractères maximum. Comme pour l'adresse e-mail, le hachage du mot de passe fourni doit correspondre au hachage du mot de passe stocké associé à l'adresse e-mail fournie.

Cependant, faire de même pour le endpoint de connexion (http://localhost:8080/auth/login) entraînerait une erreur de serveur interne (code HTTP 500). La raison est que nous utilisons une fonction validateUser dans nos règles de validation que nous n'avons pas encore créées.

Validation de l'utilisateur

Créez un nouveau répertoire appelé Validation dans le répertoire app. Dans le dossier app/Validation, créez un fichier nommé UserRules.php et ajoutez le code suivant au fichier :

 

<?php

namespace App\Validation;

use App\Models\UserModel;
use Exception;

class UserRules
{
    public function validateUser(string $str, string $fields, array $data): bool
    {
        try {
            $model = new UserModel();
            $user = $model->findUserByEmailAddress($data['email']);
            return password_verify($data['password'], $user['password']);
        } catch (Exception $e) {
            return false;
        }
    }
}

Ouvrez ensuite le fichier App/Config/Validation.php et modifiez le tableau $ruleSets pour inclure vos UserRules. $ruleSets doit ressembler à ceci :

public $ruleSets = [
    \CodeIgniter\Validation\Rules::class,
    \CodeIgniter\Validation\FormatRules::class,
    \CodeIgniter\Validation\FileRules::class,
    \CodeIgniter\Validation\CreditCardRules::class,
    \App\Validation\UserRules::class,
];

Avec les règles de validation personnalisées mises en place, la requête d'authentification fonctionne comme prévu. Testez ceci en envoyant une requête POST HTTP au endpoint http://localhost:8080/auth/login avec les détails de l'utilisateur créé précédemment :

Login

Création d'un contrôleur client

Pour le contrôleur client, nous allons spécifier les chemins dans le fichier app/Config/Routes.php. Ouvrez le fichier et ajoutez-y les chemins suivants :

$routes->get('client', 'Client::index');
$routes->post('client', 'Client::store');
$routes->get('client/(:num)', 'Client::show/$1');
$routes->post('client/(:num)', 'Client::update/$1');
$routes->delete('client/(:num)', 'Client::destroy/$1');

En procédant ainsi, votre API est capable de traiter les requêtes avec le même endpoint, mais des verbes HTTP différents.

Ensuite, dans le répertoire App/Controllers, créez un fichier appelé Client.php. Le contenu du fichier doit être le suivant :

 

<?php

namespace App\Controllers;

use App\Models\ClientModel;
use CodeIgniter\HTTP\Response;
use CodeIgniter\HTTP\ResponseInterface;
use Exception;

class Client extends BaseController
{
    /**
     * Get all Clients
     * @return Response
     */
    public function index()
    {
        $model = new ClientModel();
        return $this->getResponse(
            [
                'message' => 'Clients retrieved successfully',
                'clients' => $model->findAll()
            ]
        );
    }

    /**
     * Create a new Client
     */
    public function store()
    {
        $rules = [
            'name' => 'required',
            'email' => 'required|min_length[6]|max_length[50]|valid_email|is_unique[client.email]',
            'retainer_fee' => 'required|max_length[255]'
        ];

 $input = $this->getRequestInput($this->request);

        if (!$this->validateRequest($input, $rules)) {
            return $this
                ->getResponse(
                    $this->validator->getErrors(),
                    ResponseInterface::HTTP_BAD_REQUEST
                );
        }

        $clientEmail = $input['email'];

        $model = new ClientModel();
        $model->save($input);
        

        $client = $model->where('email', $clientEmail)->first();

        return $this->getResponse(
            [
                'message' => 'Client added successfully',
                'client' => $client
            ]
        );
    }

    /**
     * Get a single client by ID
     */
    public function show($id)
    {
        try {

            $model = new ClientModel();
            $client = $model->findClientById($id);

            return $this->getResponse(
                [
                    'message' => 'Client retrieved successfully',
                    'client' => $client
                ]
            );

        } catch (Exception $e) {
            return $this->getResponse(
                [
                    'message' => 'Could not find client for specified ID'
                ],
                ResponseInterface::HTTP_NOT_FOUND
            );
        }
    }
}

Les fonctions indexstore et show sont utilisées pour traiter les requêtes d'affichage de tous les clients, d'ajout d'un nouveau client et d'affichage d'un seul client respectivement.

Ensuite, créez deux fonctions update et destroy. La fonction update sera utilisée pour traiter les requêtes de modification d'un client. Aucun des champs n'est requis, donc toute valeur attendue qui n'est pas fournie dans la requête est supprimée avant la mise à jour du client dans la base de données. La fonction destroy traite les requêtes de suppression d'un client particulier.

    public function update($id)
    {
        try {

            $model = new ClientModel();
            $model->findClientById($id);

          $input = $this->getRequestInput($this->request);

          

            $model->update($id, $input);
            $client = $model->findClientById($id);

            return $this->getResponse(
                [
                    'message' => 'Client updated successfully',
                    'client' => $client
                ]
            );

        } catch (Exception $exception) {

            return $this->getResponse(
                [
                    'message' => $exception->getMessage()
                ],
                ResponseInterface::HTTP_NOT_FOUND
            );
        }
    }

    public function destroy($id)
    {
        try {

            $model = new ClientModel();
            $client = $model->findClientById($id);
            $model->delete($client);

            return $this
                ->getResponse(
                    [
                        'message' => 'Client deleted successfully',
                    ]
                );

        } catch (Exception $exception) {
            return $this->getResponse(
                [
                    'message' => $exception->getMessage()
                ],
                ResponseInterface::HTTP_NOT_FOUND
            );
        }
    }

Maintenant que ces éléments sont en place, notre API est prête à être consommée. Redémarrez votre application et testez-la en envoyant des requêtes (via Postman, cURL ou votre application préférée).

$ php spark serve

Ajout d'un token d'accès

Une fois le processus d'enregistrement et de connexion terminé, copiez la valeur de access_token de la réponse. Ensuite, cliquez sur l'onglet Authorization, sélectionnez Bearer token dans la liste déroulante et collez la valeur de access_token copiée précédemment :

Ajout d&#x27;un jeton d&#x27;accès

Création d'un nouveau client

Pour créer un nouveau client, envoyez une requête HTTP POST à http://localhost:8080/client :

Création d&#x27;un nouveau client

Récupération de la liste de tous les clients

Pour récupérer la liste des clients créés jusqu'à présent, envoyez une requête HTTP GET à http://localhost:8080/client :

Création d&#x27;un nouveau client

Récupération des détails d'un client par ID

Récupérez les détails d'un client particulier en envoyant une requête HTTP GET à http://localhost:8080/client/1. Dans ce cas, 1 a été utilisé pour spécifier l'id unique du client qui doit être extrait de la base de données :

Get Client
Update Client
Delete Client

Conclusion

Dans cet article, nous avons créé une API PHP à l'aide de CodeIgniter. Cela a permis l'exécution d'opérations CRUD (Create [Créer], Read [Lire], Update [Mettre à jour], Delete [Supprimer]) de base sur une ressource (client). En outre, nous avons ajouté une couche de sécurité en limitant l'accès à la ressource. Nous avons également appris à structurer notre projet de manière à séparer les concerns et à rendre notre application plus faiblement couplée.

L'intégralité du code de ce tutoriel est disponible sur GitHub. N'hésitez pas à approfondir votre exploration. J'ai hâte de voir ce que vous allez construire !

Oluyemi est un passionné de technologie, spécialisé dans le domaine de l'ingénierie des télécommunications. Son intérêt marqué pour la résolution des problèmes quotidiens rencontrés par les utilisateurs l'ont incité à se lancer dans la programmation. Depuis, il met ses compétences en matière de résolution de problèmes au service de la création de logiciels pour le Web et les appareils mobiles. Ingénieur logiciel full stack et fervent défenseur du partage des connaissances, Oluyemi a publié de nombreux contenus et articles techniques sur plusieurs blogs sur Internet. Féru de technologie, il consacre une partie de son temps libre à tester de nouveaux frameworks et langages de programmation.