Authentification JWT (JSON Web Token)
Configuration et utilisation de l'authentification par token.
Introduction
Le JWT (JSON Web Token) est un standard pour garantir la sécurité et le contexte de l'authentification dans les applications web, où le frontend est découplé du backend.
Il est courant de l'utiliser lorsque le frontend d'une application web est développé en ReactJS, AngularJS, VueJS, etc. et que le backend fournit une API REST pour fournir et recevoir des données dynamiques de l'application.
Le token (JWT) est un code sécurisé qui identifie l'utilisateur.
Avec le JWT (JSON Web Token), l'API REST du backend permet aux services privés d'obtenir le contexte d'authentification, pour savoir qui est l'utilisateur authentifié, c'est-à-dire qu'à partir du token, nous pouvons savoir quel est l'utilisateur, le token étant dans ce cas un JWT (JSON Web Token).

CORS
L'API REST peut se trouver sur un sous-domaine différent du site web, ce qui implique des configurations de CORS (Cross-Origin Resource Sharing) pour que les services puissent être accédés.
Par conséquent, si l'API REST se trouve sur un domaine/sous-domaine différent du frontend, même en utilisant JWT, il est nécessaire que le CORS soit correctement configuré.
Le CORS est une politique de sécurité de consommation de ressources sur des domaines, sous-domaines ou ports différents. Pour garantir qu'une adresse peut réellement s'intégrer avec une autre adresse.
Cela se produit parce que les navigateurs bloquent l'utilisation de services, ou d'autres types de ressources, sur des adresses externes, afin d'éviter des attaques d'obtention de données confidentielles.
Activation et configuration
Pour activer et configurer le JWT dans votre application Netuno, il est nécessaire de modifier le fichier de configuration de l'application relatif à l'environnement que vous utilisez, comme :
config/_development.jsonconfig/_production.json
Cela dépend de l'environnement configuré dans Netuno, dans le fichier
netuno/config.js, voir le paramétrage deconfig.env.
Insérez et ajustez les paramètres suivants :
...
"auth": {
"jwt": {
"enabled": true,
"secret": "SecretDoitAvoir32Caracteres!!!",
"expires": {
"access": 1440,
"refresh": 1440
}
}
},
...
Dans le paramètre secret, placez une séquence de caractères complexe et aléatoire, c'est la clé qui garantira la sécurité du chiffrement du JWT.
Les paramètres expires sont définis en minutes, par exemple : 60 équivaut à 1 heure et 1440 à un jour.
Accès restreint aux groupes
En outre, le paramètre groups peut être passé, ce qui définit le code des groupes des utilisateurs qui peuvent s'authentifier avec JWT, par exemple :
...
"auth": {
"jwt": {
...
"groups": ["client", "fournisseur"],
...
}
},
...
Dans l'exemple ci-dessus, seuls les utilisateurs appartenant aux groupes :
- Clients, le code du groupe étant
client. - Fournisseurs, le code du groupe étant
fournisseur
Seuls les utilisateurs de ces groupes peuvent s'authentifier avec JWT.
Comment obtenir l'Access Token
Pour obtenir l'Access Token du JWT, Netuno fournit le service _auth qui valide l'authentification et, si elle réussit, renvoie l'Access Token et le Refresh Token.
L'Access Token est obtenu de la manière suivante :
Fetch - JavaScript pur
Le JavaScript dans les navigateurs fournit fetch pour s'intégrer aux services de l'API REST du backend.
Voici quelques exemples de la façon de réaliser l'authentification avec JWT et le renouvellement du token.
Authentification
Exemple de la façon d'obtenir l'Access Token avec fetch :
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
username: payload.username,
password: payload.password,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log("L'authentification a échoué avec le statut inattendu :", response.status);
}
alert('Identifiants invalides.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Mon Access Token : ${res.access_token}`);
console.log(`Mon Refresh Token : ${res.refresh_token}`);
console.log(`Expire dans : ${res.expires_in} minutes`);
sessionStorage.setItem('token', JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('Un problème technique est survenu, veuillez réessayer plus tard.');
});
Notez que le token doit être conservé en session, il est donc préférable d'utiliser sessionStorage plutôt que localStorage pour plus de sécurité.
Avec le refresh_token, il est possible de générer un nouveau token avant le temps d'expiration (expires_in en minutes).
Utiliser l'Access Token dans d'autres services
Pour exécuter des services personnalisés qui exigent une authentification préalable dans les applications Netuno, vous devez passer l'Access Token dans l'en-tête du protocole HTTP, comme valeur de la clé Authorization, par exemple :
Authorization: Bearer eyJhbGciOiJIUzU...
Exemple d'exécution d'un service personnalisé via le frontend en utilisant fetch, où Authorization est passé dans les headers :
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/mon-service", {
method: 'post',
credentials: 'include',
headers: {
'Authorization': `${token.token_type} ${token.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
monParametre1: '...',
monParametre2: '...'
})
}).then((response) => {
if (response.status != 200) {
console.log(`Le service a échoué avec le statut ${response.status}.`);
if (response.status == 403) {
alert('Non authentifié.');
} else {
alert('Une erreur est survenue avec le service.');
}
return null;
}
return response.json();
}).then((res) => {
if (res) {
console.log(`Données de réponse de mon service :`, res);
alert('Service exécuté avec succès.');
}
}).catch((error)=> {
console.log(error);
alert('Un problème technique est survenu, veuillez réessayer plus tard.');
});
Notez que, dans les en-têtes HTTP, dans le paramètre Authorization, sont utilisés le token_type et l'access_token obtenus à partir de l'objet JWT précédemment enregistré lors de l'authentification dans sessionStorage.
Actualiser avec le Refresh Token
Pour actualiser le token et prolonger l'authentification, vous devez rappeler le service _auth, mais en plus du paramètre jwt: true, il est également nécessaire de passer le paramètre refresh_token avec la valeur reçue lors de l'authentification réussie précédente.
L'actualisation du token doit être effectuée avant le temps d'expiration (expires_in en minutes).
Donc, pour prolonger l'authentification et renouveler le token, voir l'exemple :
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
refresh_token: token.refresh_token,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('Le renouvellement du token a échoué avec le statut inattendu :', response.status);
}
alert("Renouvellement de l'accès avec un token invalide.");
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`Mon Nouvel Access Token : ${res.access_token}`);
console.log(`Mon Nouveau Refresh Token : ${res.refresh_token}`);
console.log(`Expire dans : ${res.expires_in} minutes`);
sessionStorage.setItem("token", JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('Un problème technique est survenu, veuillez réessayer plus tard.');
});
Lorsque vous obtenez le nouveau token, vous devez l'utiliser à la place de l'ancien dans les prochains appels aux services.
De cette façon, l'authentification est prolongée par des renouvellements successifs du token, c'est un moyen de garantir que l'utilisateur reste authentifié.
Auth Client - NPM
Pour faciliter l'intégration avec le frontend, le module NPM est mis à disposition :
Dans le frontend, vous pouvez effectuer l'installation avec Bun :
bun install @netuno/auth-client
Vous pouvez utiliser un autre gestionnaire de dépendances frontend comme PNPM, NPM, YARN, ou autre.
Configuration
Ce module dépend de @netuno/service-client, lors de la définition de l'adresse des services, par exemple :
import _service from '@netuno/service-client';
...
_service.config({
prefix: 'http://localhost:9000/services/'
});
Le auth-client utilise le préfixe du service-client pour construire en interne l'adresse du service _auth, dans ce cas, en suivant l'exemple, ce sera :
http://localhost:9000/services/_auth
Donc cette adresse est construite automatiquement en utilisant le préfixe d'adresse des services de l'API REST de l'application Netuno.
Voyez comment personnaliser les configurations du auth-client :
Dans la version ES module, config() est asynchrone. Attendez sa conclusion avant d'utiliser le client configuré :
import _auth from '@netuno/auth-client';
await _auth.config({
storage: 'local', // session est la valeur par défaut.
onLogin: () => { alert("Connecté !"); },
onLogout: () => { alert("Déconnecté !"); }
});
Si vous souhaitez que le token soit enregistré dans le localStorage, il est nécessaire de définir dans la configuration storage: 'local', car par défaut c'est storage: 'session' en utilisant le sessionStorage.
Authentification
Pour effectuer la connexion en utilisant le _auth :
import _auth from '@netuno/auth-client';
...
_auth.login({
username: "admin",
password: "motdepasse",
success: ()=> {
alert("Succès.");
},
fail: ()=> {
alert("Échec.");
}
});
Le token est enregistré automatiquement, dans session (sessionStorage) ou local (localStorage), selon la configuration, par défaut c'est session.
Actualiser le token
Exemple de la façon d'actualiser le token :
import _auth from '@netuno/auth-client';
...
_auth.refreshToken({
success: ()=> {
alert("Succès.");
},
fail: ()=> {
alert("Échec.");
}
});
Le token est enregistré automatiquement, dans session (sessionStorage) ou local (localStorage), selon la configuration, par défaut c'est session.
Supprimer le token
Exemple de la façon de terminer la session, c'est-à-dire le processus de déconnexion et de suppression du token :
import _auth from '@netuno/auth-client';
...
_auth.logout();
Le token est supprimé du sessionStorage ou du localStorage selon la configuration, la valeur par défaut est sessionStorage.
Backend - Services de l'API REST
Si l'en-tête Authorization est passé lors de l'intégration avec les services avec un token valide, il est alors possible d'identifier quel est l'utilisateur et le groupe.
Dans le backend, nous pouvons utiliser la ressource _user et la ressource _group, qui identifient respectivement quel est l'utilisateur et le groupe.
Pour voir les données de l'utilisateur authentifié, nous utilisons la ressource User, par exemple :
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
_log.info(
"Utilisateur authentifié :",
_val.map()
.set("id", _user.id())
.set("nom", _user.name())
.set("code", _user.code())
.set("tout", _user.data())
)
Pour voir le groupe de l'utilisateur authentifié, nous utilisons la ressource Group, par exemple :
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
_log.info(
"Groupe authentifié :",
_val.map()
.set("id", _group.id())
.set("nom", _group.name())
.set("code", _group.code())
.set("tout", _group.data())
)
Sécurité
Si le token est invalide, ou si l'en-tête Authorization n'est pas passé, alors Netuno garantit que le service n'est pas exécuté, sauf si le service est configuré comme accès public, par défaut les services sont privés.
Pour gérer si le service est public ou privé, configurez dans votre application dans :
server/core/_service_config.js
Étant donné qu'en développement, Netuno permet d'exécuter les services privés directement sans authentification, pour des raisons de praticité, car la plupart des services personnalisés n'ont normalement pas besoin du contexte d'authentification, ce qui simplifie globalement le développement et les tests.
Si ce n'est pas en développement, par exemple en production, alors les services ne peuvent être exécutés qu'avec authentification, comme il est évident.
Ressource d'authentification
Normalement, les ressources _user et _group sont suffisantes, mais il y a des situations où nous voulons effectuer des opérations plus avancées et dans ce cas nous avons la ressource _auth.
La ressource _auth est la ressource principale d'authentification, gère les configurations et possède toutes les opérations liées au processus d'authentification.
Pour en savoir plus, consultez la documentation de la ressource Auth.
ReAuthKit
Netuno fournit un projet de base pour créer des plateformes avec authentification en ligne, qui comprend la création de compte, la connexion, le profil, l'avatar, la récupération de mot de passe, et bien plus encore.
Découvrez-le sur GitHub :
ReAuthKit est une application Netuno avec le frontend réalisé en React, Ant.Design et React Router, et la base de données en PostgreSQL.
Avec ReAuthKit, vous accélérez considérablement la création de nouveaux projets avec authentification, car il dispose déjà de tous les mécanismes nécessaires pour tout projet nécessitant une authentification.
Conclusion
De manière simple, il est possible de configurer et d'activer l'authentification avec JWT (JSON Web Token) dans les applications.
Utilisez l'authentification avec JWT pour développer des services sécurisés dans l'API REST.
L'intégration du JWT dans le frontend peut se faire directement avec fetch.
Pour accélérer et standardiser l'implémentation, le module NPM @netuno/auth-client est mis à disposition, et peut être utilisé avec n'importe quelle technologie de frontend.
Chaque fois que vous devez identifier l'utilisateur lors de l'exécution de services de l'API REST, utilisez l'authentification avec JWT.
Dans le backend, la configuration et l'obtention de l'utilisateur authentifié sont très simples.