Samedi 21 septembre 2019

Tutoriel d’authentification PHP JWT & REST API - Connexion et inscription

https://www.techiediaries.com/php-jwt-authentication-tutorial/

Dans ce didacticiel, nous allons apprendre à ajouter une authentification JWT à notre application PHP API REST.

Nous verrons ce qu’est JWT et comment cela fonctionne. Nous verrons également comment obtenir l’en-tête d’autorisation en PHP.

Nous allons créer des points de terminaison d’API REST pour permettre aux utilisateurs de se connecter et de s’inscrire pour accéder à des ressources protégées.

Qu’est-ce que JWT?

JWT signifie JSON Web Token et comprend des informations chiffrées sur l’utilisateur pouvant être utilisées pour authentifier les utilisateurs et échanger des informations entre clients et serveurs.

Lors de la construction de l’API REST, au lieu des sessions serveur couramment utilisées dans les applications PHP, nous envoyons des jetons avec des en-têtes HTTP du serveur aux clients où ils sont conservés (généralement avec un stockage local), puis attachés à chaque demande sortante du client au serveur. . Le serveur vérifie le jeton et autorise ou refuse l’accès à la ressource de demande.

Les API RESTful sont sans état . Cela signifie que les demandes des clients doivent contenir toutes les informations nécessaires au traitement de la demande.

Si vous construisez une application API REST à l’aide de PHP, vous n’utiliserez pas la $_SESSION pour enregistrer des données sur la session du client.

Cela signifie que nous ne pouvons pas accéder à l’état d’un client (tel que l’état de connexion). Afin de résoudre le problème, le client est responsable de la gestion de l’état localement et de l’envoyer au serveur à chaque demande.

Puisque ces informations importantes sont maintenant conservées dans le stockage local du client, nous devons le protéger des yeux.

Entrez JWTs. Un jeton JWT est simplement un objet JSON contenant des informations sur l’utilisateur. Par exemple:

{
    "user": "bob",
    "email": "bob@email.com",
    "access_token": "at145451sd451sd4e5r4",
    "expire_at"; "11245454"
}

Depuis ce jeton peut être falsifié pour avoir accès à des ressources protégées. Par exemple, un utilisateur malveillant peut modifier le jeton précédent comme suit pour accéder aux ressources d’administration uniquement sur le serveur:

{
    "user": "administrator",
    "email": "admin@email.com"
}

Pour éviter cette situation, nous, JWT, devons être signés par le serveur. Si le jeton est modifié côté client, la signature du jeton ne sera plus valide et le serveur refusera l’accès à la ressource demandée.

Comment fonctionne JWT

Les jetons JWT sont simplement des informations chiffrées telles que l’identifiant, le nom d’utilisateur, l’adresse e-mail et le mot de passe.

Lorsque les utilisateurs sont connectés au serveur, ce dernier génère et renvoie un jeton JWT au client.

Ce jeton JWT sera conservé par le client à l’aide de la mémoire de stockage locale ou des cookies du navigateur et attaché à chaque demande sortante. Ainsi, si l’utilisateur demande l’accès à certaines ressources protégées, le jeton doit d’abord être vérifié par le serveur pour autoriser ou refuser l’accès.

Qu’est-ce que PHP-JWT?

php-jwt est une bibliothèque PHP qui vous permet d’encoder et de décoder des jetons Web JSON (JWT) en PHP, conformément à la norme RFC 7519.

Conditions préalables

Vous devez avoir les prérequis suivants pour pouvoir suivre ce tutoriel à partir de zéro:

  • Vous avez besoin de PHP 7, Composer et du système de base de données MySQL installés sur votre environnement de développement,
  • Vous devez avoir une connaissance de base de PHP et SQL.

Création de la base de données et des tables MySQL

Si vous avez les conditions préalables, commençons par créer la base de données MySQL. Nous utiliserons le client MySQL installé avec le serveur. Ouvrez un terminal et exécutez la commande suivante pour appeler le client:

$ mysql -u root -p

Vous devez entrer votre mot de passe MySQL lorsque vous y êtes invité.

Ensuite, créons une base de données en utilisant l’instruction SQL suivante:

mysql > create database db ;

Note : Nous supposons ici que vous avez un utilisateur MySQL appelé root . Vous devez modifier le nom d’un utilisateur MySQL existant.
Vous pouvez également utiliser phpMyAdmin ou tout autre client MySQL avec lequel vous êtes habitué à créer la base de données et les tables SQL.

Choisissons maintenant la db données db et créons une table d’ users qui contiendra les utilisateurs de notre application:

mysql> use db;
mysql> CREATE  TABLE IF NOT EXISTS `Users` (
  `id` INT  AUTO_INCREMENT ,
  `first_name` VARCHAR(150) NOT NULL ,
  `last_name` VARCHAR(150) NOT NULL ,
  `email` VARCHAR(255),
  `password` VARCHAR(255),
  PRIMARY KEY (`id`) );

Création de la structure de répertoire de projet

Créons un répertoire simple pour notre projet. Dans votre terminal, accédez à votre répertoire de travail et créez un dossier pour notre projet:

$ mkdir php-jwt-example
$ cd php-jwt-example
$ mkdir api && cd api
$ mkdir config

Nous avons d’abord créé le répertoire du projet.

Ensuite, nous avons créé un dossier api . À l’intérieur, nous avons créé un dossier de config .

Connexion à votre base de données MySQL en PHP

Accédez au dossier config et créez un fichier database.php avec le code suivant:

<?php
// used to get mysql database connection
class DatabaseService{

    private $db_host = "localhost";
    private $db_name = "mydb";
    private $db_user = "root";
    private $db_password = "";
    private $connection;

    public function getConnection(){

        $this->connection = null;

        try{
            $this->connection = new PDO("mysql:host=" . $this->db_host . ";dbname=" . $this->db_name, $this->db_user, $this->db_password);
        }catch(PDOException $exception){
            echo "Connection failed: " . $exception->getMessage();
        }

        return $this->connection;
    }
}
?>

Installer php-jwt

Passons maintenant à l’installation de la bibliothèque php-jwt aide de Composer. Dans votre terminal, exécutez la commande suivante à la racine du répertoire de votre projet:

$ composer require firebase/php-jwt

Cela téléchargera la bibliothèque php-jwt dans un dossier vendor .

Vous pouvez demander à la bibliothèque php-jwt d’encoder et de décoder les jetons JWT à l’aide du code suivant:

<?php 
require "vendor/autoload.php";
use \Firebase\JWT\JWT;

Ajout du point de terminaison de l’API d’enregistrement d’utilisateur

Dans le dossier api , créez un fichier register.php et ajoutez le code suivant pour créer un nouvel utilisateur dans la base de données MySQL:

<?php
include_once './config/database.php';

header("Access-Control-Allow-Origin: * ");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

$firstName = '';
$lastName = '';
$email = '';
$password = '';
$conn = null;

$databaseService = new DatabaseService();
$conn = $databaseService->getConnection();

$data = json_decode(file_get_contents("php://input"));

$firstName = $data->first_name;
$lastName = $data->last_name;
$email = $data->email;
$password = $data->password;

$table_name = 'Users';

$query = "INSERT INTO " . $table_name . "
                SET first_name = :firstname,
                    last_name = :lastname,
                    email = :email,
                    password = :password";

$stmt = $conn->prepare($query);

$stmt->bindParam(':firstname', $firstName);
$stmt->bindParam(':lastname', $lastName);
$stmt->bindParam(':email', $email);

$password_hash = password_hash($password, PASSWORD_BCRYPT);

$stmt->bindParam(':password', $password_hash);


if($stmt->execute()){

    http_response_code(200);
    echo json_encode(array("message" => "User was successfully registered."));
}
else{
    http_response_code(400);

    echo json_encode(array("message" => "Unable to register the user."));
}
?>

Ajout du point de terminaison de l’API de connexion utilisateur

Dans le dossier api , créez un fichier login.php et ajoutez le code suivant pour vérifier les informations d’identification de l’utilisateur et renvoyer un jeton JWT au client:

<?php
include_once './config/database.php';
require "../vendor/autoload.php";
use \Firebase\JWT\JWT;

header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");


$email = '';
$password = '';

$databaseService = new DatabaseService();
$conn = $databaseService->getConnection();



$data = json_decode(file_get_contents("php://input"));

$email = $data->email;
$password = $data->password;

$table_name = 'Users';

$query = "SELECT id, first_name, last_name, password FROM " . $table_name . " WHERE email = ? LIMIT 0,1";

$stmt = $conn->prepare( $query );
$stmt->bindParam(1, $email);
$stmt->execute();
$num = $stmt->rowCount();

if($num > 0){
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    $id = $row['id'];
    $firstname = $row['first_name'];
    $lastname = $row['last_name'];
    $password2 = $row['password'];

    if(password_verify($password, $password2))
    {
        $secret_key = "YOUR_SECRET_KEY";
        $issuer_claim = "THE_ISSUER"; // this can be the servername
        $audience_claim = "THE_AUDIENCE";
        $issuedat_claim = time(); // issued at
        $notbefore_claim = $issuedat_claim + 10; //not before in seconds
        $expire_claim = $issuedat_claim + 60; // expire time in seconds
        $token = array(
            "iss" => $issuer_claim,
            "aud" => $audience_claim,
            "iat" => $issuedat_claim,
            "nbf" => $notbefore_claim,
            "exp" => $expire_claim,
            "data" => array(
                "id" => $id,
                "firstname" => $firstname,
                "lastname" => $lastname,
                "email" => $email
        ));

        http_response_code(200);

        $jwt = JWT::encode($token, $secret_key);
        echo json_encode(
            array(
                "message" => "Successful login.",
                "jwt" => $jwt,
                "email" => $email,
                "expireAt" => $expire_claim
            ));
    }
    else{

        http_response_code(401);
        echo json_encode(array("message" => "Login failed.", "password" => $password));
    }
}
?>

Vous pouvez définir la structure de données du jeton à votre guise, par exemple (vous pouvez uniquement ajouter l’adresse électronique ou l’ID de l’utilisateur, ou les deux à l’aide d’informations supplémentaires telles que le nom de l’utilisateur), mais certaines revendications JWT réservées doivent être définies correctement car elles affectent la validité du jeton JWT, telles que:

  • iat - horodatage de l’émission du jeton.
  • iss - Chaîne contenant le nom ou l’identificateur de l’application de l’émetteur. Peut être un nom de domaine et peut être utilisé pour éliminer des jetons d’autres applications.
  • nbf - Horodatage du moment où le jeton doit commencer à être considéré comme valide. Devrait être égal ou supérieur à iat. Dans ce cas, le jeton commencera à être valide après 10 secondes après son émission.
  • exp - Horodatage du moment où le jeton doit cesser d’être valide. Doit être supérieur à iat et nbf. Dans notre exemple, le jeton expirera 60 secondes après son émission.

Ces revendications ne sont pas obligatoires, mais sont utiles pour déterminer la validité d’un jeton.

Notre charge de données JWT se trouve dans la revendication de données, nous avons ajouté le prénom, le nom, le courrier électronique et l’ID utilisateur de la base de données. Vous ne devez ajouter aucune information sensible dans la charge JWT.

La méthode JWT::encode() transformera le tableau PHP au format JSON et signera le contenu, puis encodera le dernier jeton JWT qui sera envoyé au client. Dans notre exemple, nous avons simplement codé la clé secrète qui sera utilisée pour signer la charge JWT, mais en production, vous devez vous assurer que vous utilisez une clé secrète avec une longue chaîne binaire et que vous la stockez dans un fichier de configuration.

Nous avons maintenant deux points finaux RESTful pour l’enregistrement et la connexion des utilisateurs. À ce stade, vous pouvez utiliser un client REST tel que Postman pour interconnecter avec l’API.

Tout d’abord, démarrez votre serveur PHP en utilisant la commande suivante:

$ php -S 127.0.0.1:8080

Un serveur de développement sera exécuté à partir de l’adresse 127.0.0.1:8080 .

Créons maintenant un utilisateur dans la base de données en envoyant une demande POST au noeud final api/register.php avec un corps JSON contenant le first_name , le last_name , l’ email et le password :

Exemple d'authentification PHP JWT

Vous devriez obtenir une réponse 200 HTTP avec un “User was successfully registered.” message.

Ensuite, vous devez envoyer une demande POST au noeud final /api/login.php avec un corps JSON contenant l’adresse électronique et le mot de passe utilisés pour enregistrer l’utilisateur:

Exemple d'authentification PHP JWT

Vous devriez recevoir un message “Successful login” avec un jeton JWT.

Le jeton JWT doit être conservé dans la mémoire de stockage locale de votre navigateur ou dans des cookies à l’aide de JavaScript, puis attaché à chaque demande HTTP d’envoi pour accéder à une ressource protégée sur votre serveur PHP.

Protection d’un point de terminaison API à l’aide de JWT

Voyons maintenant comment nous pouvons protéger nos systèmes d’extrémité de serveur à l’aide de jetons JWT.

Avant d’accéder à un noeud final, un jeton JWT est envoyé à chaque demande du client. Le serveur doit décoder le JWT et vérifier s’il est valide avant d’autoriser l’accès au noeud final.

Dans le dossier api , créez un fichier protected.php et ajoutez le code suivant:

<?php
include_once './config/database.php';
require "../vendor/autoload.php";
use \Firebase\JWT\JWT;

header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");


$secret_key = "YOUR_SECRET_KEY";
$jwt = null;
$databaseService = new DatabaseService();
$conn = $databaseService->getConnection();

$data = json_decode(file_get_contents("php://input"));


$authHeader = $_SERVER['HTTP_AUTHORIZATION'];

$arr = explode(" ", $authHeader);


/*echo json_encode(array(
    "message" => "sd" .$arr[1]
));*/

$jwt = $arr[1];

if($jwt){

    try {

        $decoded = JWT::decode($jwt, $secret_key, array('HS256'));

        // Access is granted. Add code of the operation here 

        echo json_encode(array(
            "message" => "Access granted:",
            "error" => $e->getMessage()
        ));

    }catch (Exception $e){

    http_response_code(401);

    echo json_encode(array(
        "message" => "Access denied.",
        "error" => $e->getMessage()
    ));
}

}
?>

Remarque : Nous supposons que le client envoie le jeton JWT dans un en-tête d’autorisation HTTP aux formats JWT <token> ou Bearer <token> . Vous pouvez également choisir d’inclure le jeton en tant que paramètre dans l’URL de la demande ou dans la charge utile de données envoyée par le client si vous ne souhaitez pas gérer les en-têtes HTTP.

Vous pouvez maintenant envoyer une demande POST avec un en-tête d’autorisation dans les formats suivants:

JWT <YOUR_JWT_TOKEN_HERE> 

Ou aussi en utilisant le format support:

Bearer <YOUR_JWT_TOKEN_HERE>

Exemple d'authentification PHP JWT

Conclusion

Dans ce tutoriel, nous avons vu comment implémenter l’authentification JWT par l’API REST dans PHP et MySQL.