L'authentification est l'une des parties les plus importantes de toute application Web. Dans ce didacticiel, nous aborderons les systèmes d’authentification par jetons et leurs différences avec les systèmes de connexion traditionnels. À la fin de ce tutoriel, vous verrez une démo entièrement fonctionnelle écrite en AngularJS et NodeJS..
Vous pouvez également trouver une large sélection de scripts et d'applications d'authentification prêts à l'emploi sur Envato Market, tels que:
Ou, si vous rencontrez des problèmes avec votre code AngularJS, vous pouvez le soumettre à araneux sur Envato Studio pour le corriger..
Avant de passer à un système d’authentification par jeton, jetons un coup d’œil sur un système d’authentification traditionnel.
Tout va bien jusqu'à ce point. L'application Web fonctionne bien et permet d'authentifier les utilisateurs afin qu'ils puissent accéder aux points de terminaison restreints. Cependant, que se passe-t-il lorsque vous souhaitez développer un autre client, par exemple pour Android, pour votre application? Serez-vous capable d'utiliser l'application actuelle pour authentifier les clients mobiles et servir du contenu restreint? Dans l'état actuel des choses, non. Il y a deux raisons principales pour cela:
Dans ce cas, vous avez besoin d’une application indépendante du client..
Dans l'authentification par jeton, les cookies et les sessions ne seront pas utilisés. Un jeton sera utilisé pour authentifier un utilisateur pour chaque demande adressée au serveur. Reconcevons le premier scénario avec l'authentification par jeton.
Il utilisera le flux de contrôle suivant:
Dans ce cas, nous n’avons pas renvoyé de session ni de cookie, et nous n’avons renvoyé aucun contenu HTML. Cela signifie que nous pouvons utiliser cette architecture pour tout client pour une application spécifique. Vous pouvez voir le schéma d'architecture ci-dessous:
Alors, quelle est cette JWT?
JWT signifie Jeton Web JSON et est un format de jeton utilisé dans les en-têtes d'autorisation. Ce jeton vous aide à concevoir la communication entre deux systèmes de manière sécurisée. Reformulons JWT en tant que "jeton porteur" aux fins de ce didacticiel. Un jeton porteur se compose de trois parties: en-tête, charge utile et signature.
Vous pouvez voir le schéma JWT et un exemple de jeton ci-dessous;
Il n'est pas nécessaire d'implémenter le générateur de jetons du support, car vous pouvez rechercher des versions qui existent déjà dans plusieurs langues. Vous pouvez en voir quelques-unes ci-dessous:
La langue | URL de la bibliothèque |
---|---|
NodeJS | http://github.com/auth0/node-jsonwebtoken |
PHP | http://github.com/firebase/php-jwt |
Java | http://github.com/auth0/java-jwt |
Rubis | http://github.com/progrium/ruby-jwt |
.NET | http://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet |
Python | http://github.com/progrium/pyjwt/ |
Après avoir couvert quelques informations de base sur l’authentification par jeton, nous pouvons maintenant passer à un exemple concret. Jetez un coup d’œil au schéma suivant, après quoi nous l’analyserons plus en détail:
https://api.yourexampleapp.com
. Si de nombreuses personnes utilisent l'application, plusieurs serveurs peuvent être nécessaires pour exécuter l'opération demandée..https://api.yourexampleapp.com
, L'équilibreur de charge va d'abord gérer une requête, puis il redirige le client vers un serveur spécifique..https://api.yourexampleapp.com
, l'application dorsale interceptera l'en-tête de demande et extraira les informations de jeton de l'en-tête d'autorisation. Une requête de base de données sera faite en utilisant ce jeton. Si ce jeton est valide et dispose de l'autorisation requise pour accéder au noeud final demandé, il continue. Si non, il retournera un code de réponse 403 (qui indique un statut interdit).L'authentification par jeton présente plusieurs avantages qui résolvent les problèmes graves. Certains d'entre eux sont les suivants:
temp
dossier, au moins pour la première fois. Disons que vous avez plusieurs serveurs et qu'une session est créée sur le premier serveur. Lorsque vous effectuez une autre demande et que celle-ci tombe sur un autre serveur, les informations de session n'existent pas et obtiennent une réponse "non autorisée". Je sais, vous pouvez résoudre cela avec une session collante. Cependant, dans l'authentification par jeton, ce cas est résolu naturellement. Il n'y a pas de problème de session collante, car le jeton de demande est intercepté à chaque demande sur n'importe quel serveur..Ce sont les avantages les plus courants de l'authentification et de la communication basées sur des jetons. C'est la fin du discours théorique et architectural sur l'authentification par jeton. Temps pour un exemple pratique.
Vous verrez deux applications pour démontrer l'authentification par jeton:
Dans le projet principal, il y aura des implémentations de services et les résultats des services seront au format JSON. Il n'y a pas de vue retournée dans les services. Dans le projet frontal, il y aura un projet AngularJS pour le code HTML frontal, puis l'application frontale sera remplie par les services AngularJS pour faire des demandes aux services principaux..
Dans le projet back-end, il y a trois fichiers principaux:
package.json
est pour la gestion de la dépendance.models \ User.js
contient un modèle utilisateur qui sera utilisé pour effectuer des opérations de base de données sur les utilisateurs.server.js
est destiné au démarrage du projet et à la gestion des demandes.C'est tout! Ce projet est très simple, vous permettant de comprendre facilement le concept principal sans faire de plongée..
"name": "angular-restful-auth", "version": "0.0.1", "dépendances": "express": "4.x", "analyseur de corps": "~ 1.0.0" , "morgan": "dernier", "mangouste": "3.8.8", "jsonwebtoken": "0.4.0", "moteurs": "noeud": "> = 0.10.0"
package.json
contient des dépendances pour le projet: Express
pour MVC, analyseur de corps
pour simuler le traitement des demandes de post dans NodeJS, Morgan
pour la journalisation des demandes, mangouste
pour que notre cadre ORM se connecte à MongoDB, et jsonwebtoken
pour créer des jetons JWT à l’aide de notre modèle d’utilisateur. Il y a aussi un attribut appelé moteurs
cela dit que ce projet est fait en utilisant NodeJS version> = 0.10.0. Ceci est utile pour les services PaaS comme Heroku. Nous allons également couvrir ce sujet dans une autre section.
var mangouste = require ('mangouste'); var Schema = mongoose.Scema; var UserSchema = new Schema (email: String, mot de passe: String, jeton: String); module.exports = mongoose.model ('User', UserSchema);
Nous avons dit que nous générerions un jeton en utilisant la charge utile du modèle utilisateur. Ce modèle nous aide à effectuer des opérations utilisateur sur MongoDB. Dans User.js
, le schéma utilisateur est défini et le modèle utilisateur est créé à l'aide d'un modèle mangouste. Ce modèle est prêt pour les opérations de base de données.
Nos dépendances sont définies et notre modèle utilisateur est défini. Nous allons donc maintenant combiner tous ceux-ci pour créer un service permettant de traiter des demandes spécifiques..
// Modules requis var express = require ("express"); var morgan = require ("morgan"); var bodyParser = require ("analyseur de corps"); var jwt = require ("jsonwebtoken"); var mangouste = require ("mangouste"); var app = express ();
Dans NodeJS, vous pouvez inclure un module dans votre projet en utilisant exiger
. Premièrement, nous devons importer les modules nécessaires dans le projet:
var port = process.env.PORT || 3001; var User = require ('./ models / User'); // Connexion à la base de données mongoose.connect (process.env.MONGO_URL);
Notre service desservira un port spécifique. Si une variable de port est définie dans les variables d’environnement système, vous pouvez l’utiliser ou vous avez défini le port. 3001
. Après cela, le modèle utilisateur est inclus et la connexion à la base de données est établie afin d'effectuer certaines opérations utilisateur. N'oubliez pas de définir une variable d'environnement-MONGO_URL
-pour l'URL de connexion à la base de données.
app.use (bodyParser.urlencoded (extended: true)); app.use (bodyParser.json ()); app.use (morgan ("dev")); app.use (function (req, res, next) res.setHeader ('Access-Control-Allow-Origin', '*'); res.setHeader ('Access-Control-Allow-Methods-Method', 'GET, POST res.setHeader ('Accès-Contrôle-Autoriser-En-têtes', 'X-Demandé avec, type de contenu, autorisation'); next (););
Dans la section ci-dessus, nous avons effectué certaines configurations pour simuler la gestion d'une requête HTTP dans NodeJS à l'aide d'Express. Nous permettons aux demandes de venir de différents domaines afin de développer un système indépendant du client. Si vous ne le permettez pas, vous allez provoquer une erreur CORS (Cross Origin Request Sharing) dans le navigateur Web..
Access-Control-Allow-Origin
autorisé pour tous les domaines.POSTER
et OBTENIR
demandes à ce service.X-Demandé Avec
et type de contenu
les en-têtes sont autorisés.app.post ('/ authenticate', fonction (req, res) User.findOne (email: req.body.email, mot de passe: req.body.password, fonction (err, utilisateur) if (err) res.json (type: false, données: "Une erreur s'est produite:" + err); else si (utilisateur) res.json (type: true, données: utilisateur, jeton: utilisateur.token); else res.json (type: false, data: "E-mail / mot de passe incorrect");););
Nous avons importé tous les modules requis et défini notre configuration. Le moment est donc venu de définir des gestionnaires de requêtes. Dans le code ci-dessus, chaque fois que vous faites un POSTER
demande à /authentifier
avec nom d'utilisateur et mot de passe, vous obtiendrez un JWT
jeton. Tout d'abord, la requête dans la base de données est traitée à l'aide d'un nom d'utilisateur et d'un mot de passe. Si un utilisateur existe, les données utilisateur seront renvoyées avec son jeton. Mais que se passe-t-il si aucun utilisateur ne correspond au nom d'utilisateur et / ou au mot de passe?
app.post ('/ signin', fonction (req, res) User.findOne (email: req.body.email, mot de passe: req.body.password, fonction (err, utilisateur) if (err) res.json (type: false, données: "Une erreur s'est produite:" + err); else si (utilisateur) res.json (type: false, données: "l'utilisateur existe déjà!"); else var userModel = new User (); userModel.email = req.body.email; userModel.password = req.body.password; userModel.save (function (err, utilisateur) user.token = jwt.sign (utilisateur , process.env.JWT_SECRET); user.save (function (err, user1) res.json (type: true, données: utilisateur1, jeton: utilisateur1.token);)); );
Quand tu fais un POSTER
demande à /se connecter
avec nom d'utilisateur et mot de passe, un nouvel utilisateur sera créé à l'aide des informations d'utilisateur publiées. Sur le 19ème
ligne, vous pouvez voir qu’un nouveau jeton JSON est généré à l’aide de la touche jsonwebtoken
module, qui a été affecté à la jwt
variable. La partie authentification est OK. Et si nous essayions d'accéder à un point de terminaison restreint? Comment pouvons-nous réussir à accéder à ce point final?
app.get ('/ me', EnsureAuthorized, function (req, res) User.findOne (token: req.token, function (err, utilisateur) if (err) res.json (type: false , data: "Une erreur est survenue:" + err); else res.json (type: true, data: utilisateur);););
Quand tu fais un OBTENIR
demande à /moi
, vous obtiendrez les informations de l'utilisateur actuel, mais pour continuer avec le point final demandé, le assurerAutorisé
la fonction sera exécutée.
function assureAuthorized (req, res, next) var bearerToken; var bearerHeader = req.headers ["autorisation"]; if (typeof bearerHeader! == 'undefined') var bearer = bearerHeader.split (""); porteurToken = porteur [1]; req.token = bearerToken; suivant(); else res.send (403);
Dans cette fonction, les en-têtes de requête sont interceptés et les autorisation
l'en-tête est extrait. S'il existe un jeton porteur dans cet en-tête, ce jeton est attribué à req.token
afin d'être utilisé tout au long de la demande, et la demande peut être continuée en utilisant suivant()
. Si un jeton n'existe pas, vous obtiendrez une réponse 403 (interdite). Revenons au handler /moi
, et utilise req.token
récupérer les données utilisateur avec ce jeton. Chaque fois que vous créez un nouvel utilisateur, un jeton est généré et enregistré dans le modèle utilisateur dans la base de données. Ces jetons sont uniques.
Nous n'avons que trois gestionnaires pour ce projet simple. Après cela, vous verrez.
process.on ('uncaughtException', function (err) console.log (err););
L'application NodeJS peut se bloquer si une erreur se produit. Avec le code ci-dessus, cette panne est évitée et un journal des erreurs est imprimé dans la console. Et enfin, nous pouvons démarrer le serveur en utilisant l'extrait de code suivant.
// Démarrer le serveur app.listen (port, fonction () console.log ("Écoute du serveur sur le port" + port););
Pour résumer:
Nous avons terminé avec le service back-end. Pour qu’elle puisse être utilisée par plusieurs clients, vous pouvez déployer cette application serveur simple sur vos serveurs, ou peut-être déployer dans Heroku. Il y a un fichier appelé Procfile
dans le dossier racine du projet. Déploiement de notre service à Heroku.
Vous pouvez cloner le projet principal à partir de ce référentiel GitHub..
Je ne discuterai pas de la création d'une application dans Heroku; vous pouvez vous référer à cet article pour créer une application Heroku si vous ne l'avez pas déjà fait auparavant. Après avoir créé votre application Heroku, vous pouvez ajouter une destination à votre projet actuel à l'aide de la commande suivante:
git à distance ajouter heroku
Vous avez maintenant cloné un projet et ajouté une destination. Après git ajouter
et git commit
, vous pouvez transmettre votre code à Heroku en effectuant git push maître heroku
. Lorsque vous réussissez à pousser un projet, Heroku exécute le npm installer
commande pour télécharger des dépendances dans le temp
dossier sur Heroku. Après cela, votre application démarrera et vous pourrez accéder à votre service en utilisant le protocole HTTP..
Dans le projet frontal, vous verrez un projet AngularJS. Ici, je ne mentionnerai que les sections principales du projet frontal, car AngularJS n’est pas quelque chose qui peut être traité dans un seul tutoriel..
Vous pouvez cloner le projet à partir de ce référentiel GitHub. Dans ce projet, vous verrez la structure de dossiers suivante:
ngStorage.js
est une bibliothèque pour AngularJS permettant de manipuler des opérations de stockage locales. En outre, il y a une disposition principale index.html
et partiels qui étendent la disposition principale sous la partiels
dossier. controllers.js
est pour définir nos actions de contrôleur dans le front-end. services.js
est pour faire des demandes de service à notre service que j'ai mentionné dans le projet précédent. Nous avons un fichier de type bootstrap appelé app.js
et dans ce fichier, les configurations et les importations de modules sont appliquées. finalement, client.js
sert à servir des fichiers HTML statiques (ou simplement index.html
, dans ce cas); cela nous aide à servir des fichiers HTML statiques lorsque vous effectuez un déploiement sur un serveur sans utiliser Apache ni aucun autre serveur Web..
…
Dans le fichier HTML de présentation principal, tous les fichiers JavaScript requis sont inclus pour les bibliothèques liées à AngularJS, ainsi que notre contrôleur personnalisé, notre service et notre fichier d'application..
'use strict'; / * Contrôleurs * / angular.module ('angularRestfulAuth') .controller ('HomeCtrl', ['$ rootScope', '$ scope', '$ location', '$ localStorage', 'principal', fonction ($ rootScope, $ scope, $ location, $ localStorage, Main) $ scope.signin = function () var formData = email: $ scope.email, mot de passe: $ scope.password Main.signin (formData, function (res) if (res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/";, fonction () $ rootScope.error = 'Échec de la connexion';); $ scope.signup = fonction () var formData = email: $ scope.email, mot de passe: $ scope.password Main.save (formData, function (res) if ( res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/", function () $ rootScope.error = 'Echec de signup ';); $ scope.me = function () Main.me (function (res) $ scope.myDetails = res;, function () $ rootScope.error =' Impossible de récupérer les détails '; ); $ scope.logout = function () Main.logout (fu nction () window.location = "/", function () alert ("Impossible de se déconnecter!"); ); ; $ scope.token = $ localStorage.token; ])
Dans le code ci-dessus, le HomeCtrl
le contrôleur est défini et certains modules requis sont injectés comme $ rootScope
et $ scope
. L’injection de dépendance est l’une des propriétés les plus puissantes d’AngularJS. $ scope
est la variable de pont entre les contrôleurs et les vues dans AngularJS, ce qui signifie que vous pouvez utiliser tester
en vue si vous avez défini dans un contrôleur spécifié comme $ scope.test =…
Dans ce contrôleur, certaines fonctions utilitaires sont définies, telles que:
se connecter
configurer un bouton de connexion sur le formulaire de connexions'inscrire
pour la gestion des formulaires d'inscriptionmoi
pour assigner le bouton Moi dans la mise en pageDans la présentation principale, dans la liste du menu principal, vous pouvez voir le contrôleur de données
attribut avec une valeur HomeCtrl
. Cela signifie que ce menu dom
l'élément peut partager la portée avec HomeCtrl
. Lorsque vous cliquez sur le bouton d’inscription du formulaire, la fonction d’inscription du fichier du contrôleur est exécutée. Dans cette fonction, le service d’inscription est utilisé à partir du Principale
service déjà injecté dans ce contrôleur.
La structure principale est vue -> contrôleur -> service
. Ce service envoie de simples requêtes Ajax au back-end afin d'obtenir des données spécifiques.
'use strict'; angular.module ('angularRestfulAuth') .factory ('Main', ['$ http', '$ localStorage', fonction ($ http, $ localStorage) var baseUrl = "votre_service_url"; fonction changeUser (utilisateur) angulaire. extend (currentUser, user); function urlBase64Decode (str) var output = str.replace ('-', '+'). replace ('_', '/'); commutateur (output.length% 4) cas 0: casse; cas 2: sortie + = '=='; casse 3: sortie + = '='; casse; défaut: lance 'Chaîne illégale base64url!'; retour window.atob (sortie); function getUserFromToken () jeton var = $ localStorage.token; var utilisateur = ; if (type jeton! == 'undefined') var encodé = token.split ('.') [1]; utilisateur = JSON. parse (urlBase64Decode (encoded)); utilisateur de retour; var currentUser = getUserFromToken (); return save: function (données, succès, erreur) $ http.post (baseUrl + '/ signin', data) .success ( succès) .error (erreur), connexion: fonction (données, réussite, erreur) $ http.post (baseUrl + '/ authenticate', données) .success (succès) .error (erreur), me: fonction ( succès, erreur) $ htt p.get (baseUrl + '/me').success(success).error(error), déconnectez-vous: function (success) changeUser (); delete $ localStorage.token; Succès(); ; ]);
Dans le code ci-dessus, vous pouvez voir les fonctions de service telles que les requêtes d’authentification. Dans controller.js, vous avez peut-être déjà compris qu'il existe des fonctions telles que Main.me
. Ce Principale
un service a été injecté dans le contrôleur et dans le contrôleur, les services appartenant à ce service sont appelés directement.
Ces fonctions sont simplement des requêtes Ajax à notre service que nous avons déployées ensemble. N'oubliez pas de mettre l'URL du service dans baseUrl
dans le code ci-dessus. Lorsque vous déployez votre service sur Heroku, vous obtenez une URL de service telle que appname.herokuapp.com
. Dans le code ci-dessus, vous définissez var baseUrl = "appname.herokuapp.com"
.
Dans la partie inscription ou connexion de l'application, le jeton de support répond à la demande et ce jeton est enregistré dans la mémoire de stockage locale. Chaque fois que vous faites une demande à un service dans le back-end, vous devez mettre ce jeton dans les en-têtes. Vous pouvez le faire en utilisant des intercepteurs AngularJS.
$ httpProvider.interceptors.push (['$ q', '$ emplacement', '$ localStorage', fonction ($ q, $ emplacement, $ localStorage) retour 'demande': fonction (config) config.headers = config.headers || ; if ($ localStorage.token) config.headers.Authorization = 'Bearer' + $ localStorage.token; retour config;, 'responseError': fonction (réponse) if (réponse. status === 401 || response.status === 403) $ location.path ('/ signature'); return $ q.reject (réponse);;]);
Dans le code ci-dessus, chaque demande est interceptée et un en-tête d'autorisation et une valeur sont placés dans les en-têtes..
Dans le projet frontal, nous avons quelques pages partielles comme se connecter
, s'inscrire
, Détails du profil
, et vb
. Ces pages partielles sont liées à des contrôleurs spécifiques. Vous pouvez voir cette relation dans app.js
:
angular.module ('angularRestfulAuth', ['ngStorage', 'ngRoute']) .config (['$ routeProvider', '$ httpProvider', fonction ($ routeProvider, $ httpProvider) $ routeProvider. lorsque ('/', templateUrl: 'partials / home.html', contrôleur: 'HomeCtrl'). when ('/ signature', templateUrl: 'partials / signin.html', contrôleur: 'HomeCtrl'). when ('/ signup ', templateUrl:' partials / signup.html ', contrôleur:' HomeCtrl '). when (' / me ', templateUrl:' partials / me.html ', contrôleur:' HomeCtrl '). sinon ( rediriger vers: '/' );
Comme vous pouvez facilement le comprendre dans le code ci-dessus, lorsque vous accédez à /
, la home.html
la page sera rendue. Un autre exemple: si vous allez à /s'inscrire
, signup.html
sera rendu. Cette opération de rendu se fera dans le navigateur, pas sur le serveur..
Vous pouvez voir comment tout ce que nous avons discuté dans ce tutoriel fonctionne dans la pratique en consultant cette démo qui fonctionne..
Les systèmes d'authentification basés sur des jetons vous aident à créer un système d'authentification / autorisation pendant que vous développez des services indépendants du client. En utilisant cette technologie, vous vous concentrez uniquement sur vos services (ou API).
La partie authentification / autorisation sera gérée par le système d'authentification basé sur des jetons en tant que couche devant vos services. Vous pouvez accéder aux services et les utiliser à partir de n'importe quel client, tels que les navigateurs Web, Android, iOS ou un client de bureau..
Et si vous recherchez des solutions toutes faites, consultez les scripts et les applications d'authentification sur Envato Market..