Introduction à JWT

JWT (JSON Web Token) est une méthode concise et autonome pour l'authentification sur le web. Il se compose de trois parties : l'en-tête, la charge utile et la signature. L'en-tête contient le type de jeton et les informations sur l'algorithme de chiffrement, la charge utile contient les données à transmettre (incluant généralement des informations d'autorisation et d'identification de l'utilisateur), et la signature est utilisée pour vérifier l'intégrité et la validité du jeton.

JWT est principalement utilisé dans deux scénarios :

  1. Authentification : Une fois qu'un utilisateur se connecte, chaque requête ultérieure inclura un JWT, permettant à l'utilisateur d'accéder aux routes, services et ressources autorisés.
  2. Échange d'informations : Le JWT est une bonne méthode pour transmettre en toute sécurité des informations entre les parties, car elles peuvent être signées numériquement, par exemple à l'aide de paires de clés publique/privée.

Format des données JWT

JWT est une manière compacte et sûre de représenter des informations à échanger entre les parties. Un JWT se compose en réalité de trois parties, séparées par des points (.``), à savoir En-tête.Payload.Signature`. Nous détaillerons ici les formats de ces trois parties.

1. En-tête

L'en-tête se compose généralement de deux parties : le type de jeton - généralement JWT, et l'algorithme de signature ou de chiffrement utilisé, tel que HMAC SHA256 ou RSA. L'en-tête est représenté en JSON, puis encodé en une chaîne à l'aide de Base64Url. Par exemple :

{
  "alg": "HS256",
  "typ": "JWT"
}

Après encodage, vous pouvez obtenir une chaîne similaire à ceci :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload

La charge utile se compose d'une série de revendications, qui sont des informations sur l'entité (généralement un utilisateur) et d'autres données. La charge utile peut inclure plusieurs revendications prédéfinies (également appelées revendications enregistrées) et des revendications personnalisées (Revendications privées).

Les revendications prédéfinies peuvent inclure :

  • iss (Émetteur) : Émetteur
  • exp (Heure d'expiration) : Heure d'expiration
  • sub (Sujet) : Sujet
  • aud (Audience) : Audience
  • iat (Heure de délivrance) : Heure de délivrance
  • nbf (Pas avant) : Heure d'entrée en vigueur
  • jti (Identifiant JWT) : Identité unique du JWT

Un exemple de charge utile pourrait ressembler à ceci (au format JSON) :

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

Ces informations seront également encodées avec Base64Url, et vous pouvez obtenir une chaîne similaire à ceci :

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0

3. Signature

La section de signature est utilisée pour signer les deux chaînes encodées mentionnées ci-dessus, afin de vérifier que le message n'a pas été altéré pendant la transmission. Tout d'abord, vous devez spécifier une clé (si vous utilisez l'algorithme HMAC SHA256), puis utiliser l'algorithme spécifié dans l'en-tête pour signer l'en-tête et la charge utile.

Par exemple, si vous avez l'en-tête et la charge utile suivants :

En-têteEncodé.ChargeUtileEncodée

Un pseudocode pour les signer en utilisant une clé pourrait être :

HMACSHA256(base64UrlEncode(en-tête) + "." + base64UrlEncode(charge utile), clé secrète)

La chaîne signée résultante pourrait être :

dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

JWT complet

En combinant ces trois parties avec un point (.) comme séparateur, on obtient le JWT complet :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY

Le standard JWT est hautement flexible et peut stocker toutes les informations dont vous avez besoin dans la charge utile (mais dans le but de réduire la taille du JWT et pour des raisons de sécurité, les informations sensibles ne doivent pas être stockées), et assure l'intégrité de ces informations grâce à la signature.

Installation de la bibliothèque JWT pour Go

La communauté Go fournit un package github.com/golang-jwt/jwt pour gérer les JWT. L'installation de cette bibliothèque est très simple, il suffit d'exécuter la commande suivante dans le répertoire de votre projet :

go get -u github.com/golang-jwt/jwt/v5

Une fois installé, vous pouvez l'inclure dans votre import comme suit :

import "github.com/golang-jwt/jwt/v5"

Création d'un jeton simple

Pour créer un jeton JWT simple en utilisant le langage Go et l'algorithme HS256, suivez ces étapes :

Tout d'abord, générez un nouvel objet Token en utilisant l'algorithme HS256 :

token := jwt.New(jwt.SigningMethodHS256)

Ensuite, utilisez la méthode SignedString pour générer une représentation de chaîne de ce jeton, en passant la clé que vous utiliserez pour la signature.

var mySigningKey = []byte("votre-secret-de-256-bits")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("Une erreur s'est produite : %v", err)
}
fmt.Println(strToken)

Cela générera un jeton simple sans aucune revendication.

Création d'un jeton avec des paramètres

Une des fonctions principales de JWT est de transporter des informations. Ces informations sont encodées dans le jeton à travers des revendications. Par exemple, créons des revendications personnalisées :

// Structure des revendications personnalisées
type MyClaims struct {
	jwt.RegisteredClaims
	Username string `json:"username"`
	Admin    bool   `json:"admin"`
}

// Créer un jeton avec des revendications personnalisées
claims := MyClaims{
    RegisteredClaims: jwt.RegisteredClaims{},
    Username: "mon_nom_utilisateur",
	Admin: true,
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// Clé secrète pour la signature
var mySigningKey = []byte("votre-secret-de-256-bits")

// Générer le jeton au format chaîne
strToken, err := token.SignedString(mySigningKey)
if err != nil {
    log.Fatalf("Une erreur s'est produite : %v", err)
}

fmt.Println(strToken)

Dans le code ci-dessus, nous avons défini la structure MyClaims pour contenir des revendications enregistrées ainsi que des informations personnalisées. Ensuite, nous utilisons toujours SignedString pour générer la chaîne de jeton.

Analyse et validation du jeton

Analyser et valider le JWT est très important, et vous pouvez le faire comme suit :

// Nous utiliserons la même structure MyClaims
tokenString := "votre-chaîne-JWT"

// Nous devons définir une fonction que le package jwt utilisera pour analyser la chaîne de jeton
keyFunc := func(t *jwt.Token) (interface{}, error) {
	// Vérifier que la méthode de signature attendue est utilisée
	if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
	    return nil, fmt.Errorf("Méthode de signature inattendue : %v", t.Header["alg"])
	}
	// Renvoyer la clé secrète pour le jeton jwt, au format []byte, conforme à la clé utilisée pour la signature précédente
	return mySigningKey, nil
}

// Analyse du jeton
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
	log.Fatalf("Erreur d'analyse : %v", err)
}

if !parsedToken.Valid {
    log.Fatalf("Jeton invalide")
}

// À ce stade, parsedToken a été vérifié, et nous pouvons lire les revendications
fmt.Printf("Utilisateur : %s, Admin : %v\n", claims.Username, claims.Admin)

Dans le code ci-dessus, nous avons fourni la chaîne de jeton, l'instance MyClaims et la fonction de clé keyFunc à la fonction jwt.ParseWithClaims. Cette fonction validera la signature et analysera le jeton, remplissant la variable claims si le jeton est valide.