Token JWT

Cet article est la suite de l’article Utilisation d’une JTable avec Rest et Utilisation d’une TableView avec Rest.

1. Introduction

JWT (JSON Web Token) est un standard ouvert décrit dans la RFC 7519 qui permet l’authentification d’un utilisateur à l’aide d’un jeton (token) signé.

2. Le fonctionnement

  • Lors du premier échange, le client envoie son couple login/mot de passe au serveur;
  • Si le couple est valide, le serveur génère un token et l’envoie au client. Ce token permettra d’authentifier l’utilisateur lors des prochains échanges;
  • Le client stocke ensuite le token en local;
  • Le token est renvoyé, par le client, pour chaque appel à l’API (via l’en-tête HTTP « Authorization ») permettant ainsi d’authentifier l’utilisateur.

Les Tokens Web JSON se composent généralement de trois parties: un en-tête header, une charge utile payload et une signature.

  a. Header

La première partie du JWT est le header. C’est un Objet JSON encodé en base64 qui représente l’en-tête du token avec 2 éléments :

  • Le type du token;
  • L’algorithme utilisé pour la signature.

  b. Payload

La seconde partie du JWT est le payload. Il s’agit tout comme le header, d’un objet JSON encodé en base64 qui représente cette fois-ci le corps du token. C’est dans cette partie que l’on mettra les informations de l’utilisateur (identifiant, rôle, etc.) ou toute autre information utile au serveur. Le standard définit trois types de propriétés (appelées claims) :

  • Propriétés réservées : Il s’agit de noms réservés définis par la spécification. On y retrouve notamment :
    • iss” (Issuer) : Permet d’identifier le serveur ou le système qui a émis le token;
    • sub” (Subject) :  Il s’agit généralement de l’identifiant de l’utilisateur que le token représente;
    • aud” (Audience) : Il s’agit généralement de l’application ou du site qui reçoit le token;
    • iat”  (Issued At) : Il s’agit de la date de génération du token;
    • exp” (Expiration Time) : Il s’agit de la date d’expiration du token.
  • Propriétés publiques : Il s’agit de noms normalisés tels que “email”, “name”, “locale”, etc. La liste complète est disponible à cette adresse;
  • Propriétés privées : Il s’agit de propriétés que vous définissez vous même pour répondre aux besoins de votre application.

  c. Signature

La dernière partie est la signature du token. Il s’agit d’un hash des deux premières parties du token réalisé en utilisant l’algorithme qui est précisé dans le header.

3. Exemple d’implémentation

Nous allons continuer notre exemple de gestion de nos produits et modifier notre serveur afin qu’il puisse gérer notre token JWT et l’utiliser pour sécurisé chacune des connections REST.

Pour cela nous allons ajouter dans notre hébergement un répertoire jwt dans lequel nous copierons les fichier .htaccess et index.php précédent. (Cf: Rest).

Nous allons devoir modifier premièrement le fichier index.php et ajouter la validation du token :

<?php
 try {
  //Clé secrete
  $secret_key = '<VOTRE CLE>';
  //Entête JWT
  $encoded_header = base64_encode('{"alg": "HS256","typ": "JWT"}');
  //connection a la base de donnees
  $pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
  $bdd = new PDO('mysql:host=localhost;dbname=<BASE DE DONNEES>', '<UTILISATEUR>', '<MOT DE PASSE>', $pdo_options);
  $id = (!empty($_GET['id']))?$_GET['id']:null;
  $tokenOk = false;

  //Validation du token
  if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
    $headers = urldecode(trim($_SERVER["HTTP_AUTHORIZATION"]));
    if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
      $tabToken = explode(".", $matches[1]);
      //Extraction de la signature originale JWT 
      $recieved_signature = $tabToken[2];
      //Concaténation les deux premiers arguments du tableau $tabToken, représentant l'entête et le payload
      $recievedHeaderAndPayload = $tabToken[0] . '.' . $tabToken[1];
      //Clé secrète encodée en base64
      $secret_key = base64_encode($secret_key);
      //Création de la nouvelle signature générer en appliquant la méthode HMAC à l'entête et au payload concaténé
      $resultedsignature = base64_encode(hash_hmac('sha256', $recievedHeaderAndPayload, $secret_key, true));
      //Vérification de la signature
      if($resultedsignature == $recieved_signature) {
        $payload = base64_decode($tabToken[1]);
        $tabPayload = json_decode($payload , true);
        //Vérification de l'adresse du serveur du token
        if(isset($tabPayload['iss']) && $tabPayload['iss']==$_SERVER['HTTP_HOST']){
          //Vérification de la date de validite du token
          if(isset($tabPayload['exp']) && time() < $tabPayload['exp']){
            $tokenOk = true;
          }else{
            echo '{"message": "TOKEN EXPIRED"}';
          }
        }else{
          echo '{"message": "TOKEN INVALID"}';
        }      
      }else{
        echo '{"message": "ERROR"}';
      }
    }
  }

  if($tokenOk){
    //Gestion des actions en fonction du verbe HTTP
    switch($_SERVER['REQUEST_METHOD']){
    ...
    }
  }else{
    echo '{"message": "ERROR"}';
  }

//Gestion des erreurs
} catch (Exception $e) {
  die('Erreur :'.$e->getMessage());
} 
?>

Ensuite nous modifierons le fichier .htaccess afin qu’Apache renvoie à PHP le contenue de la clé Authorisation. Nous y ajouterons le contenu suivant :

    ...
    RewriteCond %{HTTP:Authorization} ^(.*)
    RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
    ...

Nous compléterons le répertoire jwt par le répertoire auth et nous y ajouterons un fichier index.php qui nous permettra de générer le token JWT. Voici le contenu du fichier index.php :

<?php 
try {
  //Clé secrète
  $secret_key = '<VOTRE CLE>';
  //Entête JWT encodé en base64
  $encoded_header = base64_encode('{"alg": "HS256","typ": "JWT"}');

  //Connection à la base de donnees
  $pdo_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
  $bdd = new PDO('mysql:host=localhost;dbname=<BASE DE DONNEES>', '<UTILISATEUR>', '<MOT DE PASSE>', $pdo_options);

  //On envoie en POST le couple login/mot de passe
  if($_SERVER['REQUEST_METHOD']=="POST"){
    $data = (!empty($_POST['data']))?$_POST['data']:null; 
    if($data!=null){
      $data = urldecode($data);
      $dataTab = json_decode($data , true);
      $email = $dataTab['email'];
      $pwd = $dataTab['password'];
      $sql = "SELECT * FROM user WHERE email='".$email."' AND password='".$pwd."'";
      $response = $bdd->query($sql);
      //On vérifie que le couple login/mot de passe corresond à un utilisateur
      if($response->rowCount()==1){
        $output     = $response->fetchAll(PDO::FETCH_ASSOC);
        $idUser     = $output[0]['id'];      //Id de l'utilisateur
        $issuedAt   = time();                //L'heure courante en seconde          
        $expire     = $issuedAt + 86400;     //Ajoute 24 H à l'heure courante
        $serverName = $_SERVER['HTTP_HOST']; //Récupère l'url du serveur

        //Encodage en base64 du payload JSON
        $encoded_payload = base64_encode('{"iat": '.$issuedAt.', "iss": "'.$serverName.'", "exp": '.$expire.', "sub": '.$idUser.' }');
        //Concaténation de l'entête et du payload
        $header_payload = $encoded_header .'.'. $encoded_payload;
        //Clé secrète encodée en base64
        $secret_key = base64_encode($secret_key);
        //Création de la signature générer en appliquant l'algorithme sha256 à l'entête et au payload concaténé
        $signature = base64_encode(hash_hmac('sha256', $header_payload, $secret_key, true));
        //Création du token JWT token en concaténant la signature avec l'entête et le payload
        echo $header_payload .'.'. $signature;
      }else{
        echo '{"message": "ERROR"}';
      }
    }else{
      echo '{"message": "ERROR"}';
    }
  }else{
    echo '{"message": "ERROR"}';
  }

//Gestion des erreurs
} catch (Exception $e) {
  die('Erreur :'.$e->getMessage());
}
?>

Pour finir, il n’y à plus qu’à ajouter notre table user et notre utilisateur dans la base données MySQL :

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(50) NOT NULL,
  `password` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)) ENGINE = MYISAM;

INSERT INTO `user` (`id`, `email`, `password`) VALUES
  (1, 'john@doe.com', '123456');

La suite : Client Client Java JWT ou Client Android JWT

    Sources :
    https://static.cinay.xyz/2018/04/Using-a-JSON-Web-Token-in-PHP.html#1-encodage-base64-header-json-object
    https://dev.to/robdwaller/how-to-create-a-json-web-token-using-php-3gml
    https://www.youtube.com/watch?v=S-xBAo47W58