web-dev-qa-db-fra.com

Meilleures pratiques de SPA pour l'authentification et la gestion de session

Lors de la création d'applications de style SPA à l'aide de structures telles que Angular, Ember, React, etc., quelles sont, à notre avis, les meilleures pratiques en matière d'authentification et de gestion de session? Je peux penser à deux manières de considérer le problème.

  1. Ne le traitez pas différemment de l'authentification avec une application Web classique en supposant que l'API et l'interface utilisateur ont le même domaine d'origine.

    Cela impliquerait probablement un cookie de session, un stockage de session côté serveur et probablement un point de terminaison d'API de session que l'interface utilisateur Web authentifiée peut utiliser pour obtenir les informations des utilisateurs actuels afin de faciliter la personnalisation ou même de déterminer les rôles/capacités du côté client. Le serveur appliquerait toujours des règles protégeant l'accès aux données bien sûr, l'interface utilisateur n'utiliserait que ces informations pour personnaliser l'expérience.

  2. Traitez-le comme n'importe quel client tiers utilisant une API publique et authentifiez-vous avec un système de jeton similaire à OAuth. Ce mécanisme de jeton serait utilisé par l'interface utilisateur du client pour authentifier chaque requête adressée à l'API du serveur.

Je ne suis pas vraiment un expert ici, mais le numéro 1 semble être totalement suffisant dans la grande majorité des cas, mais j'aimerais vraiment entendre des opinions plus expérimentées.

275
Chris Nicola

Cette question a été longuement abordée sous une forme légèrement différente:

authentification RESTful

Mais cela l’adresse du côté serveur. Regardons cela du côté client. Avant de faire cela, cependant, il y a un prélude important:

Javascript Crypto est sans espoir

L'article de Matasano sur ce sujet est célèbre, mais les leçons qu'il contient sont très importantes:

http://www.matasano.com/articles/javascript-cryptography/

Résumer:

  • Une attaque de type "man-in-the-middle" peut remplacer trivialement votre code cryptographique par <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Une attaque de type "man-in-the-middle" est triviale par rapport à une page servant toutes les ressources via une connexion non SSL.
  • Une fois que vous avez SSL, vous utilisez quand même du vrai crypto.

Et d'ajouter un corollaire de mon propre:

  • Une attaque XSS réussie peut obliger un attaquant à exécuter du code sur le navigateur de votre client, même si vous utilisez SSL - par conséquent, même si vous avez bien haussé toutes les hachures, la crypto de votre navigateur peut toujours échouer si votre attaquant trouve le moyen de l'exécuter. n'importe quel code javascript sur le navigateur de quelqu'un d'autre.

Cela rend beaucoup de schémas d'authentification RESTful impossibles ou absurdes si vous envisagez d'utiliser un client JavaScript. Regardons!

HTTP Basic Auth

Tout d’abord, HTTP Basic Auth. Le plus simple des schémas: il suffit de passer un nom et un mot de passe à chaque requête.

Bien entendu, cela nécessite absolument SSL, car vous transmettez un nom et un mot de passe codés en Base64 (réversible) à chaque requête. Toute personne écoutant sur la ligne peut extraire le nom d'utilisateur et le mot de passe de manière triviale. La plupart des arguments "L'authentification de base n'est pas sécurisée" proviennent d'un emplacement de "Authentification de base sur HTTP", ce qui est une idée terrible.

Le navigateur offre une prise en charge intégrée de l’authentification HTTP de base, mais c’est moche comme un péché et vous ne devriez probablement pas l’utiliser pour votre application. L'alternative, cependant, consiste à stocker le nom d'utilisateur et le mot de passe en JavaScript.

C'est la solution la plus reposante. Le serveur n'exige aucune connaissance de l'état et authentifie chaque interaction individuelle avec l'utilisateur. Certains passionnés de REST (principalement des hommes de paille) insistent sur le fait que le maintien de tout type d'état est une hérésie et que la bouche moussera si vous songez à une autre méthode d'authentification. Il existe des avantages théoriques à ce type de conformité aux normes - pris en charge immédiatement par Apache - vous pouvez stocker vos objets sous forme de fichiers dans des dossiers protégés par des fichiers .htaccess si vous le souhaitez!

Le problème ? Vous mettez en cache côté client un nom d'utilisateur et un mot de passe. Evil.ru a ainsi une meilleure idée - même la plus élémentaire des vulnérabilités XSS pourrait amener le client à transférer son nom d’utilisateur et son mot de passe à un serveur malveillant. Vous pouvez essayer de réduire ce risque en hachant et en salant le mot de passe, mais souvenez-vous que: la crypto JavaScript est sans espoir . Vous pouvez atténuer ce risque en laissant cela au support de base du navigateur, mais .. moche comme un péché, comme mentionné précédemment.

Authentification HTTP Digest

L’authentification Digest est-elle possible avec jQuery?

Une autorisation plus "sécurisée", il s’agit d’un défi de hachage demande/réponse. Sauf que JavaScript Crypto is Hopeless , il ne fonctionne donc que sur SSL et vous devez toujours mettre en cache le nom d'utilisateur et le mot de passe côté client, ce qui le rend plus compliqué que HTTP Basic Auth mais plus sécurisé .

Authentification de requête avec des paramètres de signature supplémentaires.

Une autre autorisation plus "sécurisée", dans laquelle vous chiffrez vos paramètres avec des données de nonce et de minutage (pour vous protéger contre les attaques par répétition et par minutage) et envoyez le fichier. L'un des meilleurs exemples de ceci est le protocole OAuth 1.0, qui est, à ma connaissance, un très bon moyen d'implémenter l'authentification sur un serveur REST.

http://tools.ietf.org/html/rfc5849

Oh, mais il n'y a pas de client OAuth 1.0 pour JavaScript. Pourquoi?

JavaScript Crypto is Hopeless , rappelez-vous. JavaScript ne peut pas participer à OAuth 1.0 sans SSL, et vous devez toujours stocker le nom d'utilisateur et le mot de passe du client localement - ce qui le place dans la même catégorie que Digest Auth - c'est plus compliqué que HTTP Basic Auth mais c'est plus sécurisé .

Jeton

L'utilisateur envoie un nom d'utilisateur et un mot de passe et obtient en échange un jeton qui peut être utilisé pour authentifier les demandes.

Ceci est légèrement plus sécurisé que HTTP Basic Auth, car dès que la transaction nom d'utilisateur/mot de passe est terminée, vous pouvez supprimer les données sensibles. Il est également moins reposant, car les jetons constituent un "état" et compliquent la mise en oeuvre du serveur.

SSL encore

Le hic, cependant, est que vous devez toujours envoyer le nom d'utilisateur et le mot de passe initiaux pour obtenir un jeton. Les informations sensibles touchent toujours votre JavaScript compromis.

Pour protéger les informations d'identification de vos utilisateurs, vous devez toujours empêcher les attaquants d'entrer dans votre code JavaScript, et vous devez également envoyer un nom d'utilisateur et un mot de passe par câble. SSL requis.

Expiration du jeton

Il est courant d'appliquer des règles de jetons telles que "hé, quand ce jeton dure depuis trop longtemps, supprimez-le et faites-le s'authentifier à nouveau". ou "Je suis presque sûr que la seule adresse IP autorisée à utiliser ce jeton est XXX.XXX.XXX.XXX". Bon nombre de ces politiques sont de très bonnes idées.

Firesheeping

Cependant, l’utilisation d’un jeton sans SSL est toujours vulnérable à une attaque appelée "détournement de côté": http://codebutler.github.io/firesheep/

L'attaquant ne reçoit pas les informations d'identification de votre utilisateur, mais il peut toujours se faire passer pour votre utilisateur, ce qui peut être très mauvais.

tl; dr: L'envoi de jetons non chiffrés par fil signifie que les attaquants peuvent facilement attraper ces jetons et se faire passer pour votre utilisateur. FireSheep est un programme qui rend cela très facile.

Une zone séparée et plus sécurisée

Plus l'application que vous exécutez est volumineuse, plus il est difficile de vous assurer qu'elle ne sera pas en mesure d'injecter du code modifiant le traitement des données sensibles. Avez-vous absolument confiance en votre CDN? Vos annonceurs? Votre propre base de code?

Commun pour les détails de carte de crédit et moins commun pour nom d'utilisateur et mot de passe - certains développeurs conservent la "saisie de données sensibles" sur une page distincte du reste de leur application, une page pouvant être étroitement contrôlée et verrouillée le mieux possible, de préférence est difficile à phish utilisateurs avec.

Cookie (signifie simplement jeton)

Il est possible (et courant) de placer le jeton d'authentification dans un cookie. Cela ne change aucune des propriétés d'auth avec le jeton, c'est plus une chose pratique. Tous les arguments précédents s'appliquent toujours.

Session (toujours juste un jeton)

L'authentification de session est simplement une authentification par jeton, mais avec quelques différences qui le rendent légèrement différent:

  • Les utilisateurs commencent avec un jeton non authentifié.
  • Le backend conserve un objet 'state' lié au jeton de l'utilisateur.
  • Le jeton est fourni dans un cookie.
  • L’environnement de l’application fait abstraction des détails.

En dehors de cela, cependant, ce n'est pas différent de Token Auth, vraiment.

Cela s'éloigne encore plus d'une implémentation RESTful - avec les objets d'état, vous allez toujours plus loin dans le chemin de RPC sur un serveur avec état.

OAuth 2.0

OAuth 2.0 examine le problème suivant: "Comment le logiciel A donne-t-il au logiciel B l'accès aux données de l'utilisateur X sans que le logiciel B ait accès aux informations de connexion de l'utilisateur X".

L'implémentation est tout simplement un moyen standard pour un utilisateur d'obtenir un jeton, puis pour un service tiers d'aller "oui, cet utilisateur et ce jeton correspondent, et vous pouvez obtenir certaines de leurs données dès maintenant".

Cependant, fondamentalement, OAuth 2.0 n'est qu'un protocole de jeton. Il présente les mêmes propriétés que les autres protocoles de jetons - vous avez toujours besoin de SSL pour protéger ces jetons - il ne fait que modifier la façon dont ces jetons sont générés.

OAuth 2.0 peut vous aider de deux manières:

  • Fournir une authentification/information à d'autres
  • Obtenir l'authentification/les informations des autres

Mais finalement, vous utilisez simplement ... des jetons.

Retour à votre question

La question que vous posez est donc la suivante: "dois-je stocker mon jeton dans un cookie et que la gestion automatique des sessions de mon environnement prenne en charge les détails, ou dois-je stocker mon jeton en Javascript et gérer ces détails moi-même?"

Et la réponse est: faire tout ce qui vous rend heureux .

La chose à propos de la gestion automatique de session, cependant, est qu'il y a beaucoup de magie qui se passe dans les coulisses pour vous. Il est souvent plus agréable de contrôler vous-même ces détails.

J'ai 21 ans alors SSL est oui

L'autre réponse est: Utilisez https pour tout, sinon les brigands vont voler les mots de passe et les jetons de vos utilisateurs.

435
Curtis Lassam

Vous pouvez augmenter la sécurité du processus d'authentification en utilisant JWT (jetons Web JSON) et SSL/HTTPS.

L’authentification de base/l’ID de session peuvent être volés via:

  • Attaque MITM (Man-In-The-Middle) - sans SSL/HTTPS
  • Un intrus accédant à l'ordinateur d'un utilisateur
  • XSS

En utilisant JWT, vous chiffrez les détails de l'authentification de l'utilisateur et le stockez dans le client, et vous l'envoyez avec chaque demande à l'API, où le serveur/API valide le jeton. Il ne peut pas être déchiffré/lu sans la clé privée (que le serveur/l'API stocke secrètement)  Lisez la mise à jour .

Le nouveau flux (plus sécurisé) serait:

S'identifier

  • L'utilisateur se connecte et envoie les informations de connexion à l'API (via SSL/HTTPS)
  • L'API reçoit les identifiants de connexion
  • Si valide:
    • Enregistrer une nouvelle session dans la base de données Lire la mise à jour
    • Cryptez l'ID utilisateur, l'ID de session, l'adresse IP, l'horodatage, etc. dans un fichier JWT avec une clé privée.
  • L'API renvoie le jeton JWT au client (via SSL/HTTPS)
  • Le client reçoit le jeton JWT et le stocke dans le stockage/cookie local

Chaque demande à l'API

  • L'utilisateur envoie une requête HTTP à l'API (via SSL/HTTPS) avec le jeton JWT stocké dans l'en-tête HTTP.
  • L'API lit l'en-tête HTTP et déchiffre le jeton JWT avec sa clé privée
  • L'API valide le jeton JWT, fait correspondre l'adresse IP de la requête HTTP avec celle du jeton JWT et vérifie si la session a expiré.
  • Si valide:
    • Renvoyer la réponse avec le contenu demandé
  • Si invalide:
    • Exception de projection (403/401)
    • Signaler l'intrusion dans le système
    • Envoyer un email d'avertissement à l'utilisateur.

Mis à jour le 30.07.15:

Les charges/revendications JWT peuvent en réalité être lues sans la clé privée (secrète) et il n'est pas sécurisé de les stocker dans localStorage. Je suis désolé pour ces fausses déclarations. Cependant, ils semblent fonctionner sur un standard JWE (JSON Web Encryption) .

Je l'ai implémenté en stockant les revendications (userID, exp) dans un fichier JWT, en le signant avec une clé privée (secrète) que l'API/backend ne connaît que et je l'ai stocké sous la forme d'un cookie HttpOnly sécurisé sur le client. De cette manière, il ne peut pas être lu via XSS ni manipulé, sinon le JWT échoue lors de la vérification de la signature. De plus, en utilisant un cookie sécurisé HttpOnly , vous vous assurez que le cookie est envoyé uniquement via des requêtes HTTP (non accessibles au script) et uniquement via une connexion sécurisée (HTTPS).

Mis à jour le 17.07.16:

Les JWT sont par nature apatrides. Cela signifie qu'ils s'invalident/expirent. En ajoutant l'ID de session dans les revendications du jeton, vous lui attribuez un état, car sa validité ne dépend plus seulement de la vérification de la signature et de la date d'expiration, mais également de l'état de la session sur le serveur. Cependant, l’avantage est que vous pouvez facilement invalider des jetons/sessions, ce que vous ne pouviez pas faire auparavant avec des JWT sans état.

50
Gaui

Je voudrais aller pour le second, le système de jetons.

Saviez-vous que ember-auth ou ember-simple-auth ? Ils utilisent tous les deux le système basé sur les jetons, comme ember-simple-auth déclare:

Une bibliothèque légère et discrète pour l'implémentation de l'authentification par jeton dans les applications Ember.js. http://ember-simple-auth.simplabs.com

Ils ont une gestion de session et sont faciles à intégrer aux projets existants.

Il existe également un Ember _ exemple de version de ember-simple-auth: Exemple d'utilisation de ember-app-kit utilisant ember-simple-auth pour l'authentification OAuth2.

7
DelphiLynx