passeport.js RESTful auth


155

Comment gère-t-on l'authentification (locale et Facebook, par exemple) à l'aide de passeport.js, via une API RESTful plutôt que via une interface Web?

Les problèmes spécifiques concernent la gestion du passage des données des rappels à une réponse RESTful (JSON) par rapport à l'utilisation d'un res.send typique ({data: req.data}), la configuration d'un point de terminaison initial / login qui redirige vers Facebook (/ login ne peut pas être accessible via AJAX, car ce n'est pas une réponse JSON - c'est une redirection vers Facebook avec un rappel).

J'ai trouvé https://github.com/halrobertson/test-restify-passport-facebook , mais j'ai du mal à le comprendre.

De plus, comment passeport.js stocke-t-il les informations d'authentification? Le serveur (ou est-ce un service?) Est soutenu par MongoDB, et je m'attendrais à ce que les informations d'identification (connexion et hachage salé de pw) y soient stockées, mais je ne sais pas si passeport.js a ce type de capacité.


Puisque vous êtes nouveau sur Node, commencez facilement et consultez l'exemple d'application pour passport-facebook. Une fois que cela fonctionne, l'étape suivante consiste à commencer à comprendre comment fonctionne Passport et comment il stocke les informations d'identification. Le connecter à Restify ( voir ici pour une version mise à jour de celui que vous mentionnez) serait l'une des dernières étapes (ou vous pourriez implémenter l'interface REST dans Express).
robertklep

Réponses:


312

Il y a beaucoup de questions posées ici, et il semble que même si les questions sont posées dans le contexte de Node et de passport.js, les vraies questions concernent plus le flux de travail que la façon de le faire avec une technologie particulière.

Utilisons l'exemple de configuration @Keith, modifié un peu pour plus de sécurité:

  • Le serveur Web à https://example.comsert une application cliente Javascript sur une seule page
  • Le service Web RESTful sur https://example.com/apifournit une prise en charge du serveur à l'application client riche
  • Serveur implémenté dans Node et passport.js.
  • Le serveur a une base de données (de tout type) avec une table "utilisateurs".
  • Le nom d'utilisateur / mot de passe et Facebook Connect sont proposés comme options d'authentification
  • Le client riche transforme les requêtes REST en https://example.com/api
  • Il peut y avoir d'autres clients (applications téléphoniques, par exemple) qui utilisent le service Web à https://example.com/apimais ne connaissent pas le serveur Web à https://example.com.

Notez que j'utilise HTTP sécurisé. C'est à mon avis un must pour tout service disponible à l'air libre, car des informations sensibles telles que les mots de passe et les jetons d'autorisation passent entre le client et le serveur.

Authentification par nom d'utilisateur / mot de passe

Voyons d'abord comment fonctionne l'authentification ancienne.

  • L'utilisateur se connecte à https://example.com
  • Le serveur sert une application Javascript riche qui rend la page initiale. Certainshwere dans la page il y a un formulaire de connexion.
  • La plupart des sections de cette application à page unique n'ont pas été remplies de données car l'utilisateur n'est pas connecté. Toutes ces sections ont un écouteur d'événement sur un événement de «connexion». Tout cela est du côté client, le serveur ne connaît pas ces événements.
  • L'utilisateur entre son identifiant et son mot de passe et appuie sur le bouton d'envoi, ce qui déclenche un gestionnaire Javascript pour enregistrer le nom d'utilisateur et le mot de passe dans des variables côté client. Ensuite, ce gestionnaire déclenche l'événement "login". Encore une fois, tout cela est une action côté client, les informations d'identification n'ont pas encore été envoyées au serveur .
  • Les écouteurs de l'événement "login" sont appelés. Chacun de ceux-ci doit maintenant envoyer une ou plusieurs requêtes à l'API RESTful à https://example.com/apipour obtenir les données spécifiques à l'utilisateur à afficher sur la page. Chaque demande qu'ils envoient au service Web comprendra le nom d'utilisateur et le mot de passe, éventuellement sous la forme d'une authentification HTTP Basic , car le service RESTful n'est pas autorisé à maintenir l'état du client d'une demande à l'autre. Le service Web étant sur HTTP sécurisé, le mot de passe est chiffré en toute sécurité pendant le transit.
  • Le service Web https://example.com/apireçoit un tas de demandes individuelles, chacune avec des informations d'authentification. Le nom d'utilisateur et le mot de passe de chaque demande sont vérifiés par rapport à la base de données utilisateur et, s'ils sont trouvés corrects, la fonction demandée s'exécute et les données sont renvoyées au client au format JSON. Si le nom d'utilisateur et le mot de passe ne correspondent pas, une erreur est envoyée au client sous la forme d'un code d'erreur HTTP 401.
  • Au lieu de forcer les clients à envoyer un nom d'utilisateur et un mot de passe à chaque demande, vous pouvez avoir une fonction "get_access_token" dans votre service RESTful qui prend le nom d'utilisateur et le mot de passe et répond avec un jeton, qui est une sorte de hachage cryptographique unique et expirant date qui lui est associée. Ces jetons sont stockés dans la base de données avec chaque utilisateur. Ensuite, le client envoie le jeton d'accès dans les demandes suivantes. Le jeton d'accès sera ensuite validé par rapport à la base de données au lieu du nom d'utilisateur et du mot de passe.
  • Les applications clientes non navigateur comme les applications téléphoniques font la même chose que ci-dessus, elles demandent à l'utilisateur de saisir ses informations d'identification, puis de les envoyer (ou un jeton d'accès généré à partir d'elles) avec chaque demande au service Web.

Le point important à retenir de cet exemple est que les services Web RESTful nécessitent une authentification à chaque demande .

Une couche de sécurité supplémentaire dans ce scénario ajouterait une autorisation d'application cliente en plus de l'authentification utilisateur. Par exemple, si vous avez le client Web, les applications iOS et Android qui utilisent tous le service Web, vous voudrez peut-être que le serveur sache lequel des trois est le client d'une demande donnée, quel que soit l'utilisateur authentifié. Cela peut permettre à votre service Web de restreindre certaines fonctions à des clients spécifiques. Pour cela, vous pouvez utiliser des clés API et des secrets, voir cette réponse pour quelques idées à ce sujet.

Authentification Facebook

Le flux de travail ci-dessus ne fonctionne pas pour Facebook connect car la connexion via Facebook a un tiers, Facebook lui-même. La procédure de connexion nécessite que l'utilisateur soit redirigé vers le site Web de Facebook où les informations d'identification sont entrées en dehors de notre contrôle.

Voyons donc comment les choses changent:.

  • L'utilisateur se connecte à https://example.com
  • Le serveur sert une application Javascript riche qui rend la page initiale. Quelque part dans la page, il y a un formulaire de connexion qui comprend un bouton "Connexion avec Facebook".
  • L'utilisateur clique sur le bouton "Se connecter avec Facebook", qui est juste un lien qui redirige vers (par exemple) https://example.com/auth/facebook.
  • L' https://example.com/auth/facebookitinéraire est géré par passeport.js (voir la documentation )
  • Tout ce que l'utilisateur voit, c'est que la page change et qu'il se trouve maintenant sur une page hébergée sur Facebook où il doit se connecter et autoriser notre application Web. Ceci est complètement hors de notre contrôle.
  • L'utilisateur se connecte à Facebook et donne la permission à notre application, donc Facebook redirige maintenant vers l'URL de rappel que nous avons configurée dans la configuration de passeport.js, qui suivant l'exemple de la documentation esthttps://example.com/auth/facebook/callback
  • Le gestionnaire de passeport.js pour l' https://example.com/auth/facebook/callbackitinéraire appellera la fonction de rappel qui reçoit le jeton d'accès Facebook et certaines informations utilisateur de Facebook, y compris l'adresse e-mail de l'utilisateur.
  • Avec l'e-mail, nous pouvons localiser l'utilisateur dans notre base de données et stocker le jeton d'accès Facebook avec lui.
  • La dernière chose que vous faites dans le rappel Facebook est de rediriger vers l'application client riche, mais cette fois, nous devons transmettre le nom d'utilisateur et le jeton d'accès au client afin qu'il puisse les utiliser. Cela peut être fait de plusieurs manières. Par exemple, des variables Javascript peuvent être ajoutées à la page via un moteur de modèle côté serveur, ou bien un cookie peut être renvoyé avec ces informations. (merci à @RyanKimber pour avoir signalé les problèmes de sécurité liés au passage de ces données dans l'URL, comme je l'avais suggéré initialement).
  • Alors maintenant, nous démarrons l'application à page unique une fois de plus, mais le client a le nom d'utilisateur et le jeton d'accès.
  • L'application cliente peut déclencher immédiatement l'événement "login" et laisser les différentes parties de l'application demander les informations dont elles ont besoin au service Web.
  • Toutes les demandes envoyées à https://example.com/apiincluront le jeton d'accès Facebook pour l'authentification, ou le propre jeton d'accès de l'application généré à partir du jeton Facebook via une fonction «get_access_token» dans l'API REST.
  • Les applications sans navigateur ont la tâche un peu plus difficile ici, car OAuth nécessite un navigateur Web pour la connexion. Pour vous connecter à partir d'un téléphone ou d'une application de bureau, vous devrez démarrer un navigateur pour effectuer la redirection vers Facebook, et pire encore, vous besoin d'un moyen pour le navigateur de renvoyer le jeton d'accès Facebook à l'application via un mécanisme.

J'espère que cela répond à la plupart des questions. Bien sûr, vous pouvez remplacer Facebook par Twitter, Google ou tout autre service d'authentification basé sur OAuth.

Je serais intéressé de savoir si quelqu'un a un moyen plus simple de gérer cela.


5
Merci pour votre réponse détaillée. Juste une question: vous dites cela Every single request they send to the web service will include the username and password, et pourtant vous dites you can have a "get_access_token" function in your RESTful service. Il semble contradictoire de dire que REST doit être sans état, mais le stockage des jetons d'accès côté serveur est OK, car cet acte de stockage des jetons d'accès signifie que le serveur est maintenant avec état. J'apprécierais toute clarification ou justification à ce sujet. Merci! :)
ryanrhee

6
Quand je pense à l'exigence apatride, je pense à l'état d'une session particulière avec un client. Je ne vois pas le stockage des données associées à un utilisateur dans une base de données, comme un mot de passe ou un jeton d'accès, comme étant un «état», du moins pas un «état de session». Votre serveur a évidemment besoin de savoir qui sont les utilisateurs et leurs mots de passe, mais il s'agit de données d'application qui ne sont pas associées à une session. Cela dit, beaucoup de gens utilisent des cookies dans des services supposés REST, donc le degré de rigueur avec lequel vous voulez adhérer à la spécification REST dépend vraiment de chaque implémenteur.
Miguel

1
@Dexter: Dans le cas de connexion traditionnel, un utilisateur entre son nom d'utilisateur et son mot de passe dans un formulaire et lorsqu'il clique sur le bouton Soumettre, ces informations sont publiées sur un serveur Web. Dans ce cas, cela ne se produit pas, l'utilisateur remplit le formulaire et lorsqu'il clique sur Soumettre, un gestionnaire Javascript (un événement onClick dans le bouton d'envoi) capture les données et les conserve dans le contexte client. Je n'ai pas d'exemple prêt à montrer, mais faites attention à une deuxième partie de ce tutoriel sur mon blog où je vais vous montrer comment cela est fait: blog.miguelgrinberg.com/post
Miguel

2
Il s'agit d'un article très bien pensé, mais qui contient un oubli ou une erreur important. Lors de la gestion de la connexion Facebook (ou Github, Twitter, etc.), il serait de loin préférable de renvoyer le jeton au client dans un cookie, puis de supprimer le cookie côté client une fois qu'il est découvert. La transmission du jeton dans le cadre de la chaîne d'URL ajoutera cette URL à l'historique du navigateur et (si les choses sont mal gérées) pourrait entraîner la demande de cette URL par le navigateur. L'un ou l'autre rend le jeton visible. Quiconque aurait ensuite copié l'URL pourrait usurper cet utilisateur dans votre application.
Ryan Kimber

1
@Nathan: l'authentification de base sur https est sécurisée, donc oui, c'est un mécanisme acceptable.
Miguel

11

J'apprécie beaucoup l'explication de @ Miguel avec le flux complet dans chaque cas, mais j'aimerais en ajouter dans la partie Authentification Facebook.

Facebook fournit un SDK Javascript que vous pouvez utiliser pour obtenir directement le jeton d'accès côté client, qui est ensuite transmis au serveur et utilisé pour extraire davantage toutes les informations utilisateur de Facebook. Vous n'avez donc pas besoin de redirection.

De plus, vous pouvez également utiliser le même point de terminaison API pour les applications mobiles. Utilisez simplement le SDK Android / iOS pour Facebook, obtenez le jeton d'accès Facebook du côté client et transmettez-le au serveur.

En ce qui concerne la nature sans état, comme expliqué, lorsque get_access_token est utilisé pour générer un jeton et transmis au client, ce jeton est également stocké sur le serveur. C'est donc aussi bon qu'un jeton de session et je pense que cela le rend avec état?

Juste mes 2 cents ...


1
Ceci est très important, car de cette façon, vous pouvez faire une authentification ajax sur une seule page en utilisant Facebook. Le jeton est ÉGALEMENT sécurisé en tant qu'authentification de session, la seule différence est qu'un jeton peut être passé à un autre domaine et la session n'est utilisée que dans un domaine spécifique. Lorsque vous utilisez expressjs et passeport, vous pouvez créer une API qui enregistre un état et utiliser l'authentification de session.
jperelli

L'api javascript est géniale si vous souhaitez authentifier l'utilisateur pour effectuer des actions contre Facebook, mais inutile en soi si vous souhaitez valider l'utilisateur par rapport à votre serveur / base de données pour autant que je sache.
James Westgate

4
Vous pouvez également utiliser la méthode décrite par Miguel ci-dessus, mais ensuite émettre votre propre jeton JWT en tant que cookie lors de la redirection du client dans le rappel d'authentification. Cela permet le meilleur des deux mondes - votre application d'une seule page peut être concernée par un seul type d'authentification (JWT), tout en gardant le même niveau de sécurité et en offrant la flexibilité de prendre en charge l'une des connexions sociales sans avoir à utiliser API JavaScript spécifiques pour chaque réseau social (Facebook, Twitter, LinkedIn, Google, etc.). Il vous permet également de maintenir la prise en charge de style AJAX pour le nom d'utilisateur / mot de passe et l'accès REST.
Ryan Kimber

Le SDK Javascript de Facebook ne fonctionne actuellement pas avec Chrome iOS. Peut-être un problème pour certains.
demisx

@RyanKimber pouvez-vous écrire un petit repo git ou similaire où cela est fait à titre d'exemple, je
suis

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.