web-dev-qa-db-fra.com

Utilisation sécurisée des JWT avec protection CSRF et jetons d'actualisation

J'implémente des JWT dans mon application et j'aimerais les rendre aussi sécurisés que possible. Je vais exposer tout ce que je prévois, j'apprécierais grandement toutes les suggestions quant à la sécurité de cette mise en œuvre. Ceci est mon site, j'ai un accès complet à tous les aspects de celui-ci, à la fois front-end et back-end.

Ce sera un SPA, utilisant une API pour accéder au back-end. J'utilise des JWT pour enregistrer les appels DB avec chaque hit d'API.

JWTs

Les JWT sont stockés dans un access_token biscuit. Ils sont d'abord signés puis chiffrés à l'aide de jose-jwt . L'algorithme de signature est HS256, le cryptage est DIR. Ils incluent l'ID de l'utilisateur, une revendication d'expiration exp et plusieurs autres revendications personnalisées. Les JWT expirent dans 30 minutes et le cookie JWT expire dans 7 jours.

(version courte):

  • JWT stocké dans un cookie
  • JWT inclut l'ID utilisateur
  • JWT signé puis chiffré
  • JWT exp revendication définie à 30 minutes dans le futur
  • Le cookie JWT devrait expirer 7 jours plus tard

Protection CSRF

Les JWT incluent une revendication cst qui stocke un jeton CSRF généré de manière aléatoire. Le jeton CSRF est envoyé dans le corps de la réponse lors de la connexion et lorsqu'un nouveau JWT est émis. Le jeton CSRF est stocké dans le stockage local du navigateur. Il est envoyé avec chaque demande et validé par rapport à la valeur dans le JWT.

(version courte):

  • JWT inclut un jeton CSRF généré aléatoirement
  • Jeton CSRF envoyé lors de la connexion et stocké dans localStorage
  • Jeton CSRF envoyé dans l'en-tête de demande de toutes les demandes
  • Jeton CSRF d'en-tête par rapport au jeton CSRF dans le JWT

Jetons rafraîchissants

Comme les JWT expirent dans 30 minutes, il est nécessaire de les rafraîchir. Le JWT inclut une revendication rfs qui stocke un jeton d'actualisation aléatoire. Ce jeton d'actualisation est également stocké dans la base de données (une table distincte des utilisateurs pour autoriser plusieurs sessions). Si le JWT a expiré (sur la base de sa revendication exp), la base de données est vérifiée pour s'assurer que l'utilisateur est toujours valide (par exemple, compte non supprimé, mot de passe non modifié, etc.). Si l'utilisateur est valide, le jeton d'actualisation est vérifié et un nouveau jeton JWT/CSRF est généré et renvoyé dans la réponse. Si l'utilisateur n'est pas valide, le access_token est renvoyé avec une valeur arbitraire comme 0, et son expiration est réglée sur le passé afin que le navigateur l'efface. Le jeton CSRF est renvoyé vide, il sera donc effacé de localStorage. Tous les jetons d'actualisation pour l'utilisateur sont supprimés de la base de données.

(version courte):

  • Si JWT a expiré, vérifiez la base de données utilisateur pour vérifier que l'utilisateur est toujours valide
  • SI VALIDE:
    • Comparez le jeton d'actualisation avec la base de données (supposez qu'il correspond pour le reste)
    • Générer un nouveau jeton d'actualisation, écraser la valeur précédente dans la base de données
    • Réémettre JWT avec une nouvelle date exp
    • Transmettez le jeton de rafraîchissement et stockez-le dans localStorage
  • SI NON VALIDE:
    • Effacer le cookie JWT en a) définissant une valeur non valide, b) définissant l'expiration au passé
    • Dites au navigateur d'effacer le jeton CSRF localStorage
    • Effacer tous les jetons d'actualisation pour l'utilisateur de la base de données

TL rapide et sale; DR

  • Lors de la connexion, ajoutez un jeton CSRF aléatoire au JWT.
  • Renvoyez ce même jeton CSRF au client dans le corps de la réponse.
  • Stockez le jeton CSRF dans localStorage.
  • Incluez un jeton d'actualisation dans le JWT.
  • Définissez le cookie JWT pour expirer après 1 semaine.
  • Définissez la revendication exp JWT sur 30 minutes.
  • Si la revendication JWT a expiré, vérifiez le jeton d'actualisation par rapport à la base de données pour vous assurer que l'utilisateur est toujours valide.
  • SI L'UTILISATEUR EST VALIDE:
    • Problème JWT mis à jour avec un nouveau jeton CSRF et un nouveau jeton d'actualisation.
    • Définissez l'expiration du cookie JWT sur une semaine dans le futur. (réémettez le cookie, en gros)
    • Envoyez un nouveau jeton CSRF dans le corps de la réponse, remplacez la valeur localeStorage existante.
  • SI L'UTILISATEUR N'EST PAS VALIDE:
    • Renvoyer le cookie JWT avec le même nom mais sans contenu.
    • Définissez l'expiration des cookies sur une date arbitraire dans le passé.
    • Dites au navigateur d'effacer la valeur localStorage.
11
vaindil

Vous semblez mélanger plusieurs technologies opposées différentes et ne précisez pas pourquoi vous avez choisi ces technologies et pourquoi elles contrôlent les menaces contre lesquelles vous essayez de vous protéger.

Les JWT sont stockés dans un cookie access_token. Ils sont d'abord signés puis chiffrés à l'aide de jose-jwt.

Y a-t-il une raison pour laquelle ils sont cryptés? La signature est utilisée lorsque vous souhaitez vérifier l'intégrité des données - c'est-à-dire qu'elles n'ont été modifiées par personne sans la clé. Le cryptage est utilisé lorsque vous souhaitez protéger la confidentialité des données, c'est-à-dire qu'elles ne peuvent être lues par quiconque sans clé. S'il n'y a rien dans le jeton qui ne devrait pas pouvoir être lu par l'utilisateur final, il n'y a aucune raison de chiffrer.

Les JWT incluent une revendication cst qui stocke un jeton CSRF généré de manière aléatoire. Le jeton CSRF est envoyé dans le corps de la réponse lors de la connexion et lorsqu'un nouveau JWT est émis. Le jeton CSRF est stocké dans le stockage local du navigateur. Il est envoyé avec chaque demande et validé par rapport à la valeur dans le JWT.

Cela ressemble à une implémentation du contrôle Double Submit Cookie CSRF. localStorage est protégé par même politique d'origine empêchant une autre session Web dans le navigateur de l'utilisateur d'accéder au jeton.

Assurez-vous que le jeton CSRF a au moins 128 bits d'entropie.

Comme les JWT expirent dans 30 minutes, il est nécessaire de les rafraîchir. Le JWT inclut une revendication rfs qui stocke un jeton d'actualisation aléatoire. Ce jeton d'actualisation est également stocké dans la base de données (une table distincte des utilisateurs pour autoriser plusieurs sessions). Si le JWT a expiré (sur la base de sa revendication exp), la base de données est vérifiée pour s'assurer que l'utilisateur est toujours valide (par exemple, compte non supprimé, mot de passe non modifié, etc.). Si l'utilisateur est valide, le jeton d'actualisation est vérifié et un nouveau jeton JWT/CSRF est généré et renvoyé dans la réponse.

C'est là que les choses se mélangent. Votre modèle de sécurité doit décider des sessions côté client ou des sessions côté serveur. Les JWT sont généralement employés pour les premiers. Autrement dit, si vous avez un jeton dans le client signé par une clé uniquement disponible pour le serveur, votre application doit avoir confiance que la présence de ce jeton indique une session valide si elle n'a pas expiré et que la signature est valide.

L'inconvénient de cette méthode est qu'il est difficile de révoquer des jetons. Si un compte d'utilisateur est compromis et qu'un utilisateur modifie son mot de passe, toutes les sessions de l'attaquant seront toujours actives pendant 30 minutes car elles disposent toujours d'un jeton valide, non expiré et signé.

Le correctif consiste à implémenter des sessions côté serveur à la place. Par exemple, vous gardez une trace des sessions dans une table de base de données, ce qui signifie qu'elles peuvent être déconnectées instantanément en supprimant la ligne dans la base de données. Le jeton de session peut être une chaîne aléatoire d'entropie de 128 bits, fournie sous forme de cookie côté client et stockée hachée avec SHA-256 côté serveur pour atténuer toute fuite de données de votre base de données. Vous pouvez toujours envoyer une date d'expiration en texte brut avec votre cookie, afin que le client sache que lorsqu'il doit actualiser le jeton. par exemple. un cookie HttpOnly pour le jeton et un cookie non HttpOnly contenant l'expiration afin que le JavaScript côté client puisse le lire. Les cookies HttpOnly peuvent aider à atténuer l'impact d'une faille XSS.

Par conséquent, si vous effectuez le suivi des sessions côté serveur, il y a peu d'avantages à disposer d'un côté client JWT signé. Un code supplémentaire signifie plus de surface d'attaque et plus de chances d'introduire des vulnérabilités dans le code excédentaire. La complexité d'une application est souvent inverse de la sécurité.

Si vous utilisez Double Submit Cookie en combinaison avec une session côté serveur pour l'état de la session, il est judicieux d'utiliser un cookie différent pour le jeton CSRF. Cela vous permettra d'ajouter le jeton CSRF en tant qu'en-tête en utilisant du code côté client sans risquer votre jeton d'identifiant de session. Notez que si vous définissez des en-têtes personnalisés et n'implémentez pas CORS, alors cela peut atténuer quelque peu CSRF . Il est également recommandé d'utiliser un jeton, car des technologies comme Flash ont tendance à perturber la sécurité du navigateur (Flash signifie plus de code en cours d'exécution, plus de code donne plus de surface d'attaque, plus de code signifie plus de risques de vulnérabilités).

11
SilverlightFox

Cela me semble assez solide (la solution CSRF est mignonne aussi) jusqu'à la partie Refresh Token.

L'un des avantages que je vois dans un système basé sur JWT est que les jetons d'accès expirent régulièrement. Cela signifie qu'un jeton d'accès JWT compromis ne donne accès à un attaquant que pendant une période de quelques minutes ou heures.

Si vous incluez le jeton d'actualisation dans le JWT, un attaquant qui compromet le JWT peut facilement l'utiliser pour s'actualiser lui-même, ce qui ne lui donne aucune date d'expiration effective.

Un jeton d'accès ne doit pas pouvoir accorder un nouveau jeton d'accès. Seul un jeton d'actualisation (ou une authentification complète) doit pouvoir accorder un nouveau jeton d'accès.

4
ProdigySim

Agrégation des commentaires:

  • JWT en tant qu'identifiant au porteur dans une application à page unique qui communique avec une API côté serveur via ajax est logique.
  • L'utilisation de cookies dans une application à page unique n'a pas de sens. Si toutes les demandes au goulot d'étranglement de l'API du serveur via une fonction ajax, cette fonction peut inclure JWT dans un en-tête. S'il n'y a pas de cookies, alors il n'y a pas de CSRF, car les requêtes des trames attaquantes ne passeront pas par la fonction ajax
  • L'utilisation de scripts tiers est bien sûr omniprésente, mais c'est néanmoins un gros risque pour la sécurité. Les scripts tiers fonctionnent dans le même contexte javascript et DOM et peuvent voir l'intégralité du DOM. S'ils sont malveillants, il existe de très nombreuses options d'attaque. Si possible, éliminez les scripts tiers ou assurez-vous qu'ils sont vérifiés, en utilisant l'intégrité des sous-ressources ou d'autres moyens (par exemple, surveillez-les pour les changements).
  • localStorage suit la même politique d'origine et peut être traité comme un cache client pour les données DOM ou d'autorisation. Dans un SPA, le JWT peut être conservé dans localStorage, car il suit le même modèle de sécurité que le DOM.
  • Un jeton d'actualisation peut être conservé dans le JWT
  • Les durées d'expiration et d'actualisation de JWT sont difficiles à commenter dans l'abstrait. Il est important de trouver le bon équilibre entre sécurité et convivialité, de ne pas obliger les utilisateurs à sauter à travers les cerceaux à moins qu'il n'y ait une bonne raison, et de fournir aux utilisateurs la bonne assurance en cas de problème.

Quelques autres choses à faire:

  • Appliquez https et utilisez des en-têtes de protocole de ressources associés comme les options x-frame, la sécurité de transport stricte et la politique de sécurité du contenu
  • Conservez certaines métadonnées de la version particulière de l'application à page unique dans le JWT et exigez une actualisation après la mise à jour de l'application
  • Conservez les métadonnées du client particulier dans le JWT. N'autorisez pas l'utilisation d'un navigateur de bureau JWT à partir d'un navigateur mobile.
  • Avoir la capacité du serveur de révoquer tous les JWT en attente et d'actualiser les jetons associés à un utilisateur. Si un utilisateur avait des JWT et des jetons d'actualisation liés à son téléphone et son ordinateur de bureau et vous dit qu'il a perdu son téléphone, vous souhaiterez probablement invalider son JWT de téléphone et de bureau et actualiser ses jetons.
  • Avoir une forte autorisation de niveau objet/opération sur l'API du serveur, c'est-à-dire vérifier l'intégrité du JWT, confirmer que l'autorisation qu'il n'a pas été révoquée, puis vérifier que les revendications qu'il inclut fournissent l'accès approprié à l'API, avant d'effectuer toute Fonctionnement de l'API.
2
Jonah Benton