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

309

Lors de la création d'applications de style SPA à l'aide de cadres tels que Angular, Ember, React, etc., que pensent les gens des meilleures pratiques d'authentification et de gestion de session? Je peux penser à deux façons d'envisager d'aborder 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 d'avoir 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 atteindre pour obtenir les informations utilisateur actuelles pour aider à la personnalisation ou peut-être même 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 utiliserait simplement ces informations pour personnaliser l'expérience.

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

Je ne suis pas vraiment un expert ici, mais # 1 semble être complètement suffisant pour la grande majorité des cas, mais j'aimerais vraiment entendre des opinions plus expérimentées.

Chris Nicola
la source
Je fais comme ça, stackoverflow.com/a/19820685/454252
allenhwkim

Réponses:

478

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

Authentification RESTful

Mais cela résout le problème du côté serveur. Examinons 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 à ce sujet est célèbre, mais les leçons qu'il contient sont assez importantes:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Résumer:

  • Une attaque man-in-the-middle peut remplacer trivialement votre code crypto par <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Une attaque man-in-the-middle est triviale contre une page qui sert n'importe quelle ressource via une connexion non SSL.
  • Une fois que vous avez SSL, vous utilisez de toute façon une vraie crypto.

Et pour ajouter un corollaire à moi:

  • Une attaque XSS réussie peut aboutir à ce qu'un attaquant exécute du code sur le navigateur de votre client, même si vous utilisez SSL - donc même si vous avez chaque trappe abattue, la cryptographie de votre navigateur peut toujours échouer si votre attaquant trouve un moyen d'exécuter tout code javascript sur le navigateur de quelqu'un d'autre.

Cela rend beaucoup de schémas d'authentification RESTful impossibles ou stupides si vous avez l'intention d'utiliser un client JavaScript. Regardons!

Authentification HTTP de base

Tout d'abord, HTTP Basic Auth. Le plus simple des schémas: passez simplement un nom et un mot de passe à chaque demande.

Bien sûr, cela nécessite absolument SSL, car vous transmettez un nom et un mot de passe encodés en Base64 (réversiblement) à chaque demande. Quiconque écoute sur la ligne peut extraire trivialement le nom d'utilisateur et le mot de passe. La plupart des arguments "Basic Auth is unsecure" proviennent d'un endroit de "Basic Auth over HTTP" qui est une idée horrible.

Le navigateur fournit un support d'authentification HTTP de base intégré, mais il est laid comme un péché et vous ne devriez probablement pas l'utiliser pour votre application. Cependant, l'alternative consiste à cacher le nom d'utilisateur et le mot de passe en JavaScript.

C'est la solution la plus RESTful. Le serveur ne nécessite aucune connaissance de l'état et authentifie chaque interaction individuelle avec l'utilisateur. Certains amateurs de REST (principalement des hommes de paille) insistent sur le fait que le maintien de toute sorte d'état est une hérésie et moussera à la bouche si vous pensez à une autre méthode d'authentification. Il existe des avantages théoriques à ce type de conformité aux normes - il est pris en charge par Apache prêt à l'emploi - vous pouvez stocker vos objets sous forme de fichiers dans des dossiers protégés par des fichiers .htaccess si votre cœur le souhaite!

Le problème ? Vous mettez en cache côté client un nom d'utilisateur et un mot de passe. Cela donne à evil.ru une meilleure solution: même les vulnérabilités les plus élémentaires de XSS pourraient amener le client à transmettre son nom d'utilisateur et son mot de passe à un serveur malveillant. Vous pouvez essayer d'atténuer ce risque en hachant et en salant le mot de passe, mais rappelez-vous: JavaScript Crypto est sans espoir . Vous pouvez atténuer ce risque en le laissant à la prise en charge de l'authentification de base du navigateur, mais .. laid comme un péché, comme mentionné précédemment.

HTTP Digest Auth

L'authentification Digest est-elle possible avec jQuery?

Une authentification plus "sécurisée", c'est un défi de hachage de demande / réponse. Sauf que JavaScript Crypto est sans espoir , il ne fonctionne 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 pas plus sécurisé .

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

Une autre authentification plus "sécurisée", où vous cryptez vos paramètres avec des données nonce et de synchronisation (pour vous protéger contre les attaques répétées et de synchronisation) et envoyez le. L'un des meilleurs exemples de cela est le protocole OAuth 1.0, qui est, pour autant que je sache, un moyen assez efficace pour implémenter l'authentification sur un serveur REST.

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

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

JavaScript Crypto est désespéré , 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 ce n'est pas plus sécurisé .

Jeton

L'utilisateur envoie un nom d'utilisateur et un mot de passe, et en échange obtient 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 de nom d'utilisateur / mot de passe est terminée, vous pouvez supprimer les données sensibles. Il est également moins RESTful, car les jetons constituent un "état" et rendent l'implémentation du serveur plus compliquée.

SSL toujours

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

Pour protéger les informations d'identification de votre utilisateur, vous devez toujours garder les attaquants hors de votre JavaScript, et vous devez toujours envoyer un nom d'utilisateur et un mot de passe sur le câble. SSL requis.

Expiration du jeton

Il est courant d'appliquer des stratégies de jeton telles que «hé, lorsque ce jeton existe depuis trop longtemps, jetez-le et faites-vous authentifier à nouveau». ou "Je suis pratiquement sûr que la seule adresse IP autorisée à utiliser ce jeton est XXX.XXX.XXX.XXX". Beaucoup 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 «sidejacking»: http://codebutler.github.io/firesheep/

L'attaquant n'obtient pas les informations d'identification de votre utilisateur, mais il peut toujours prétendre être votre utilisateur, ce qui peut être assez mauvais.

tl; dr: L'envoi de jetons non chiffrés via le câble signifie que les attaquants peuvent facilement attraper ces jetons et prétendre être votre utilisateur. FireSheep est un programme qui rend cela très simple.

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

Plus l'application que vous exécutez est grande, plus il est difficile de garantir absolument qu'elle ne pourra pas injecter du code qui modifie la façon dont vous traitez les données sensibles. Faites-vous entièrement confiance à votre CDN? Vos annonceurs? Votre propre base de code?

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

Cookie (signifie simplement Token)

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 (signifie toujours juste Token)

L'authentification de session n'est qu'une authentification par jeton, mais avec quelques différences qui la font paraître quelque chose de légèrement différent:

  • Les utilisateurs commencent avec un jeton non authentifié.
  • Le backend maintient un objet «état» qui est lié au jeton d'un utilisateur.
  • Le jeton est fourni dans un cookie.
  • L'environnement d'application résume les détails loin de vous.

Hormis 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 de plus en plus loin sur le chemin du RPC ordinaire sur un serveur avec état.

OAuth 2.0

OAuth 2.0 examine le problème de "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 maintenant".

Fondamentalement, cependant, OAuth 2.0 n'est qu'un protocole symbolique. 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 modifie simplement la façon dont ces jetons sont générés.

OAuth 2.0 peut vous aider de deux manières:

  • Fourniture d'authentification / d'informations à des tiers
  • Obtention de l'authentification / des informations des autres

Mais en fin de compte, vous utilisez simplement ... des jetons.

Retour à votre question

Ainsi, la question que vous posez est "dois-je stocker mon jeton dans un cookie et faire en sorte que la gestion automatique des sessions de mon environnement s'occupe des détails, ou dois-je stocker mon jeton en Javascript et gérer moi-même ces détails?"

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

La chose à propos de la gestion automatique des sessions, cependant, c'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 donc SSL est oui

L'autre réponse est: utilisez https pour tout ou les brigands voleront les mots de passe et les jetons de vos utilisateurs.

Curtis Lassam
la source
3
Très bonne réponse. J'apprécie l'équivalence entre les systèmes d'authentification par jeton et l'authentification par cookie de base (qui est souvent intégrée au cadre Web). C'est en quelque sorte ce que je cherchais. J'apprécie que vous ayez également abordé tant de questions potentielles. À votre santé!
Chris Nicola
11
Je sais que ça fait un moment mais je me demande si cela devrait être étendu pour inclure JWT? auth0.com/blog/2014/01/07/…
Chris Nicola
14
Token It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST requiert que le serveur soit sans état. Un côté client stocké par jeton ne représente pas l'état de manière significative pour le serveur. (2) Un code côté serveur légèrement plus compliqué n'a rien à voir avec RESTfulness.
soupdog
10
lol_nope_send_it_to_me_insteadJ'ai adoré le nom de cette fonction: D
Leo
6
Une chose que vous semblez ignorer: les cookies sont sécurisés XSS lorsqu'ils sont marqués httpOnly, et peuvent être verrouillés davantage avec secure et samesite. Et la gestion des cookies a été beaucoup plus longue === plus de bataille durcie. S'appuyer sur JS et le stockage local pour gérer la sécurité des jetons est un jeu de dupes.
Martijn Pieters
57

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

L'ID d'authentification / session de base peut être volé via:

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

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

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 informations de connexion
  • Si valide:
    • Enregistrer une nouvelle session dans la base de données Lire la mise à jour
    • Chiffrez l'ID utilisateur, l'ID de session, l'adresse IP, l'horodatage, etc. dans un 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 stocke dans localStorage / cookie

Chaque demande à l'API

  • L'utilisateur envoie une demande 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 demande 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:
    • Lancer l'exception (403/401)
    • Signaler l'intrusion dans le système
    • Envoyez un e-mail d'avertissement à l'utilisateur.

Mise à jour 30.07.15:

La charge utile / les revendications JWT peuvent en fait être lues sans la clé privée (secrète) et il n'est pas sûr de la stocker dans localStorage. Je suis désolé pour ces fausses déclarations. Cependant, ils semblent fonctionner sur une norme JWE (JSON Web Encryption) .

J'ai implémenté cela en stockant les revendications (userID, exp) dans un JWT, je l'ai signé avec une clé privée (secrète) que l'API / backend ne connaît que et l'a stocké en tant que cookie HttpOnly sécurisé sur le client. De cette façon, il ne peut pas être lu via XSS et ne peut pas être manipulé, sinon le JWT échoue à la vérification de signature. De plus, en utilisant un cookie HttpOnly sécurisé , vous vous assurez que le cookie est envoyé uniquement via des requêtes HTTP (non accessible au script) et uniquement via une connexion sécurisée (HTTPS).

Mise à jour le 17.07.16:

Les JWT sont par nature apatrides. Cela signifie qu'ils s'invalident / expirent eux-mêmes. En ajoutant le SessionID dans les revendications du jeton, vous le rendez avec état, car sa validité ne dépend plus uniquement de la vérification de la signature et de la date d'expiration, il dépend é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 auparavant avec des JWT sans état.

Gaui
la source
1
En fin de compte, un JWT n'est encore «qu'un jeton» du point de vue de la sécurité, je pense. Le serveur pourrait toujours associer l'ID utilisateur, l'adresse IP, l'horodatage, etc. à un jeton de session opaque et il ne serait ni plus ni moins sécurisé qu'un JWT. Cependant, la nature sans état de JWT facilite la mise en œuvre.
James
1
@James the JWT a l'avantage d'être vérifiable et capable de transporter des détails clés. Ceci est assez utile pour divers scénarios d'API, comme lorsque l'authentification inter-domaines est requise. Quelque chose pour lequel une session ne sera pas aussi bonne. Il s'agit également d'une spécification définie (ou du moins en cours), utile pour les implémentations. Cela ne veut pas dire que c'est mieux que toute autre bonne mise en œuvre de jetons, mais elle est bien définie et pratique.
Chris Nicola
1
@Chris Oui, je suis d'accord avec tous vos points. Cependant, le flux décrit dans la réponse ci-dessus n'est pas intrinsèquement un flux plus sécurisé comme revendiqué en raison de l'utilisation d'un JWT. En outre, le JWT n'est pas révocable dans le schéma décrit ci-dessus, sauf si vous associez un identifiant au JWT et stockez l'état sur le serveur. Sinon, vous devez soit obtenir régulièrement un nouveau JWT en demandant un nom d'utilisateur / mot de passe (mauvaise expérience utilisateur) ou émettre un JWT avec un délai d'expiration très long (mauvais si le jeton est volé).
James
1
Ma réponse n'est pas correcte à 100%, car JWT peut en fait être déchiffré / lu sans la clé privée (secrète) et ce n'est pas sûr de le stocker dans localStorage. J'ai implémenté cela en stockant les revendications (userID, exp) dans un JWT, je l'ai signé avec une clé privée (secrète) que l'API / backend ne connaît que et l'a stocké sous forme de cookie HttpOnly sur le client. De cette façon, il ne peut pas être lu par XSS. Mais vous devez utiliser HTTPS car le jeton pourrait être volé avec une attaque MITM. Je mettrai à jour ma réponse pour y réfléchir.
Gaui
1
@vsenko Le cookie est envoyé à chaque demande du client. Vous n'accédez pas au cookie à partir du JS, il est lié à chaque demande HTTP du client à l'API.
Gaui
7

J'irais pour le deuxième, le système de jetons.

Connaissiez-vous ember-auth ou ember-simple-auth ? Ils utilisent tous les deux le système basé sur des jetons, comme les états ember-simple-auth:

Une bibliothèque légère et discrète pour implémenter l'authentification basée sur les jetons dans les applications Ember.js. http://ember-simple-auth.simplabs.com

Ils ont la gestion des sessions et sont faciles à connecter aux projets existants.

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

DelphiLynx
la source