Post

JSON Web Token (JWT) en PHP

JSON Web Token (JWT) pour échanger de l'information de manière sécurisée via un jeton signé.

JSON Web Token (JWT) en PHP

JSON Web Token (JWT)

JWT ou 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é
Voir article Sécuriser une API REST (Partie 1) : Théorie qui explique le JWT.

  • 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.

jwt

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

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.
1
2
3
4
{
  "typ":"JWT",
  "alg":"HS256"
}

HS256 indique que ce jeton token est signé avec HMAC-SHA256.

Payload

Objet JSON encodé en base64 qui représente le corps du token pour y mettre toute information utile au serveur. Le standard définit trois types de propriétés (appelées claims) :

  • réservées : Il s’agit de noms réservés définis par la spécification. On y retrouve notamment :
    • “iss” (Issuer) : Identifie le serveur ou le système qui a émis le token;
    • “sub” (Subject) : Identifiant de l’utilisateur que le token représente;
    • “aud” (Audience) : Application ou site qui reçoit le token;
    • “iat” (Issued At) : Date de génération du token;
    • “exp” (Expiration Time) : Date d’expiration du token.
  • publiques : Noms normalisés tels que “email”, “name”, “locale”, etc… (JSON Web Token Claims , voir tableau ci-joint)
Claim Name Claim Description
iss Issuer
sub Subject
aud Audience
exp Expiration Time
nbf Not Before
iat Issued At
jti JWT ID
name Full name
given_name Given name(s) or first name(s)
family_name Surname(s) or last name(s)
middle_name Middle name(s)
nickname Casual name
preferred_username Shorthand name by which the End-User wishes to be referred to
profile Profile page URL
picture Profile picture URL
website Web page or blog URL
email Preferred e-mail address
email_verified True if the e-mail address has been verified
gender Gender
birthdate Birthday
zoneinfo Time zone
locale Locale
phone_number Preferred telephone number
phone_number_verified True if the phone number has been verified
address Preferred postal address
updated_at Time the information was last updated
azp Authorized party - the party to which the ID Token was issued
nonce Value used to associate a Client session with an ID Token
auth_time Time when the authentication occurred
at_hash Access Token hash value
c_hash Code hash value
acr Authentication Context Class Reference
amr Authentication Methods References
sub_jwk Public key used to check the signature of an ID Token
cnf Confirmation
sip_from_tag SIP From tag header field parameter value
sip_date SIP Date header field value
sip_callid SIP Call-Id header field value
sip_cseq_num SIP CSeq numeric header field parameter value
sip_via_branch SIP Via branch header field parameter value
orig Originating Identity String
dest Destination Identity String
mky Media Key Fingerprint String
  • privées : Propriétés à définir pour répondre aux besoins de votre application.

/!\le payload ne doit pas contenir de données sensibles

Signature

Hash des deux premières parties du token réalisé en utilisant l’algorithme qui est précisé dans le header,l’algorithme utilisé ci-dessous est HS256 (HMAC-SHA-256).

1
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), 'secret')

L’algorithme utilise une clé secrète (détenue par le serveur), utilisée pour signer les tokens mais également s’assurer de la validité de ceux-ci en vérifiant leur signature.

Expiration d’un token

JWT possède une date d’expiration (propriété “exp” du payload).
La durée de validité d’un JWT va dépendre du type de données échangées (quelques minutes pour des données sensibles à plusieurs heures pour les non sensibles).

Pour éviter à l’utilisateur une réauthentification lorsque le JWT expire,utiliser un “refresh token”.
Lorsque l’utilisateur s’identifie à l’aide de son couple login/mot de passe, le serveur génère en plus du JWT, un token aléatoire à usage unique attaché à l’utilisateur (généralement sauvegardé en base de données).

jwt

Utilisation refresh token

Lorsque le JWT est expiré :

  • Le client (application ou site) envoie une requête avec le JWT expiré et le “refresh token”
  • Le serveur vérifie que le JWT est expiré, que le “refresh token” est valide et que celui-ci est bien associé à l’utilisateur du JWT expiré
  • Le serveur génère un nouveau JWT et “refresh token” (lié à l’utilisateur) et invalide l’ancien “refresh token” (par exemple suppression de la base de données ou un champ indiquant que ce token n’est plus valide)
  • Le serveur renvoie le nouveau JWT et “refresh token” au client;
  • Le client sauvegarde le JWT et le “refresh token”;
  • Le client peut utiliser le nouveau JWT.

Possibilité d’ajouter une date d’expiration au refresh token .

Créer un jeton Web JSON (Json Web Token) en PHP

La section suivante décrit étape par étape comment créer un jeton Web JSON en PHP. Pour créer un jeton Web JSON, nous utilisons les deux méthodes PHP décrites ci-dessous:

  • string json_encode (mixed $value [, int $options = 0 [, int $depth = 512 ]] ) — Renvoie la représentation JSON d’une valeur où l’argument $value peut être n’importe quel type sauf une ressource
  • string base64_encode ( string $data ) — code les données données en base64.

1-Encodage base64 “header JSON Object”

La première étape dans la création d’un JWT est de définir la première partie du jeton token, qui est l’en-tête header, puis l’encoder en base64, en utilisant les deux méthodes PHP
Création ,droits et édition du fichier jwt-header.php

1
touch jwt-header.php && chmod +x jwt-header.php && nano jwt-header.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/php
<?php

//setting the header: 'alg' => 'HS256' indicates that this token is signed using HMAC-SHA256
$header = [
                  'typ' => 'JWT',
                  'alg' => 'HS256'
        ];

// Returns the JSON representation of the header
$header = json_encode($header);	

//encodes the $header with base64.	
$header = base64_encode($header);

//list the resulted header
print_r($header);
?> 

Exécution et sortie

1
./jwt-header.php

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2-Encodage base64 “payload JSON Object”

Après avoir créé l’en-tête, nous devons créer lepayload:

1
$payload = ["country" => "France", "name" => "Ansel-Arsenault", "email" => AnselArsenault@jourrapide.com ];

Pour coder le payload, nous utilisons à nouveau les deux méthodes json_encode et base64_encode

1
2
$payload = json_encode($payload);		
$payload = base64_encode($payload); 

3-Concaténer header et payload avec le point “.” comme séparateur.

Après avoir encodé le payload, nous devons concaténer header avec payload comme ceci:

1
$header.$payload

Créer le php

1
touch jwt-header-payload.php && chmod +x jwt-header-payload.php && nano jwt-header-payload.php

Le listing suivant affiche l’en-tête et la charge utile (header et payload)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/php
<?php

$header = [
           'typ' => 'JWT',
		   'alg' => 'HS256'
		];

$header = json_encode($header);		
$header = base64_encode($header);

//setting the payload
$payload = [      
		"country" => "France",
		"name" => "Ansel-Arsenault",
		"email" => "AnselArsenault@jourrapide.com "
		  ];

// Returns the JSON representation of the payload
$payload = json_encode($header);		

//encodes the $payload with base64.	
$payload = base64_encode($header);

echo "$header.$payload";
?> 

Exécution et sortie

1
./jwt-header-payload.php

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ZXlKMGVYQWlPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5

4-Calculer la “signature” de l’en-tête “header” et de la charge utile “payload”

La prochaine étape dans la création d’un JWT en PHP consiste à définir la signature en la générant en utilisant la méthode HMAC.

1
$signature = hash_hmac('sha256','$header.$payload', $key, true);

Vous remarquerez que l’argument $key est utilisé pour générer la signature. Il représente votre mot de passe personnel et est utilisé pour valider la signature, comme vous le verrez dans cet article. Dans ce cas, la clé $key is ‘Ansel-Signe-Arsenault’.

1
touch jwt-signature.php && chmod +x jwt-signature.php && nano jwt-signature.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/php
<?php

//setting the personal key identification
$key = 'Ansel-Signe-Arsenault';

$header = [
                        'typ' => 'JWT',
		   'alg' => 'HS256'
		  ];

$header = json_encode($header);		
$header = base64_encode($header);

$payload = [      
		"country" => "France",
		"name" => "Ansel-Arsenault",
		"email" => "AnselArsenault@jourrapide.com "
		  ];

$payload = json_encode($header);		
$payload = base64_encode($header);

// Generates a keyed hash value using the HMAC method
$signature = hash_hmac('sha256','$header.$payload', $key, true);

echo $signature;
?> 

Exécution et sortie

1
./jwt-signature.php

���[�2�1�� ���sÿ>Z�a�@��›)

5-Concaténer “signature”,”header” et “payload”.

Avant d’effectuer la dernière étape dans la création du jeton token final, nous avons besoin de coder également la signature base64, en utilisant cette ligne de code:

1
$signature = base64_encode($signature);

La dernière étape dans la création de notre jeton token est de concaténer toutes les chaînes: l’en-tête header, la charge utile payload et la signature en utilisant cette ligne:

1
$token = "$header.$payload.$signature";

Le fichier php suivant rassemble toutes les étapes décrites ci-dessus et produit le jeton résultant

1
touch jwt-token.php && chmod +x jwt-token.php && nano jwt-token.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/php
<?php

// base64 encodes the header json
$encoded_header = base64_encode('{"alg": "HS256","typ": "JWT"}');

// base64 encodes the payload json
$encoded_payload = base64_encode('{"country": "France","name": "Ansel Arsenault","email": "AnselArsenault@jourrapide.com "}');

// base64 strings are concatenated to one that looks like this
$header_payload = $encoded_header . '.' . $encoded_payload;

//Setting the secret key
$secret_key = ']~5YL,ymj8Xe72';
//Clé secrète encodée base64
$secret_key = base64_encode($secret_key);

// Creating the signature, a hash with the s256 algorithm and the secret key. The signature is also base64 encoded.
$signature = base64_encode(hash_hmac('sha256', $header_payload, $secret_key, true));

// Creating the JWT token by concatenating the signature with the header and payload, that looks like this:
$jwt_token = $header_payload . '.' . $signature;

//listing the resulted  JWT
echo $jwt_token;
?> 

Exécution et sortie

1
./jwt-token.php

token
eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0=.eyJjb3VudHJ5IjogIkZyYW5jZSIsIm5hbWUiOiAiQW5zZWwgQXJzZW5hdWx0IiwiZW1haWwiOiAiQW5zZWxBcnNlbmF1bHRAam91cnJhcGlkZS5jb20gIn0=.nGiEj+uzBf6eCtsFZCF0kbJUWdUDpg6SvA+zcPzNUPM=

Maintenant, si nous voulons vérifier que tout s’est bien passé, nous pouvons tester le jeton dans https://jwt.io/ en le collant dans la section Encodé. Vous verrez ensuite sur le côté droit, dans la section Décodage, toutes vos données. Mais pour que votre signature puisse le valider, vous devez entrer votre clé secrète au bas de la page, dans la section Vérifier la signature, comme vous pouvez le voir à partir de la figure suivante:

Texte alternatif

Vérification de la signature

Dans cette section, après avoir créé un jeton token en PHP, nous voulons vérifier qu’une signature reçue est correcte. La liste suivante obtient le jeton token, en extrait la signature, crée une nouvelle signature en utilisant l’en-tête header et la charge utile payload extraite du jeton reçu (en utilisant également la clé secrète $secret_key), puis comparez la signature. Si tout s’est bien passé, vous devriez obtenir un message de réussite: Créer le php
touch jwt-verif.php && chmod +x jwt-verif.php && nano jwt-verif.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/php
<?php

$recievedJwt = 'eyJhbGciOiAiSFMyNTYiLCJ0eXAiOiAiSldUIn0=.eyJjb3VudHJ5IjogIlJvbWFuaWEiLCJuYW1lIjogIk9jdGF2aWEgQW5naGVsIiwiZW1haWwiOiAib2N0YXZpYWFuZ2hlbEBnbWFpbC5jb20ifQ==.gbB+B063g+kwsoc4L3B1Bu2wM+VEBElwPiLOb0fj2SE=';

//Setting the secret key
$secret_key = ']~5YL,ymj8Xe72';
//Clé secrète encodée base64
$secret_key = base64_encode($secret_key);

// Split a string by '.' 
$jwt_values = explode('.', $recievedJwt);

// extracting the signature from the original JWT 
$recieved_signature = $jwt_values[2];

// concatenating the first two arguments of the $jwt_values array, representing the header and the payload
$recievedHeaderAndPayload = $jwt_values[0] . '.' . $jwt_values[1];

// creating the Base 64 encoded new signature generated by applying the HMAC method to the concatenated header and payload values
$resultedsignature = base64_encode(hash_hmac('sha256', $recievedHeaderAndPayload, $secret_key, true));

// checking if the created signature is equal to the received signature
if($resultedsignature == $recieved_signature) {

	// If everything worked fine, if the signature is ok and the payload was not modified you should get a success message
	echo "Success";
}
?> 

Exécution et sortie
./jwt-verif.php
Success

Au cours de cet article, vous avez appris comment créer un jeton Web JSON en PHP, comment le tester et comment vérifier un jeton reçu s’il est correct. En conclusion, un jeton Web JSON est assez facile à créer et à valider vous-même. La clé secrète ne devra pas tomber dans de mauvaises mains, sinon vous ne pouvez plus faire confiance aux signatures.

Cet article est sous licence CC BY 4.0 par l'auteur.