web-dev-qa-db-fra.com

Stratégie de jeton JWT pour le frontend et le backend

J'écris une application avec un front end dans emberjs et backend/server-side dans un serveur nodejs. J'ai configuré emberjs pour qu'un utilisateur puisse se connecter/s'inscrire avec un tiers Oauth (google, Twitter, Facebook). J'ai un backend écrit dans le serveur express nodejs qui héberge les API RESTful.

Je n'ai pas de base de données connectée à emberjs et je ne pense pas que je devrais de toute façon car c'est strictement du code côté client. Je prévois d'utiliser JWT pour communiquer entre le côté client et le côté serveur. Lorsqu'un utilisateur se connecte avec son oauth cred, je récupère un objet JSON du fournisseur avec uid, name, login, access_token et d'autres détails.

Je n'arrive pas à choisir une stratégie pour gérer l'inscription des utilisateurs. Il n'y a pas de processus d'inscription car c'est OAuth. Donc, le flux est si l'utilisateur n'est pas dans ma base de données, créez-le. Je ne supporte pas l'authentification par e-mail/mot de passe. Quel serait le flux lorsqu'un utilisateur se connecte avec un fournisseur OAuth pour la première fois? Emberjs devrait-il envoyer tous les détails au backend à chaque connexion afin que le backend puisse ajouter de nouveaux utilisateurs au db?

Que devrait faire partie de mon corps JWT? Je pensais que l'uid et le fournisseur avaient fourni un jeton d'accès. Un problème auquel je peux penser ici est que le jeton d'accès spécifique au fournisseur peut changer. L'utilisateur peut révoquer le jeton du site du fournisseur et se réinscrire avec emberjs.

Je suis prêt à écrire le front-end dans tout autre framework côté client javascript si cela facilite les choses.

30
ed1t

Si nous parlons non seulement de travailler mais également d'authentification sans état sécurisée, vous devrez envisager une stratégie appropriée avec les jetons access et refresh.

  1. Le jeton d'accès est un jeton qui fournit un accès à une ressource protégée. Expiration ici pourrait être installé environ en ~ 1 heure (dépend de vos considérations).

  2. Le jeton d'actualisation est un jeton spécial qui doit être utilisé pour générer des access token en cas d'expiration ou de mise à jour de la session utilisateur. De toute évidence, vous devez le faire durer longtemps (en comparaison avec access token) et sécurisé autant que possible. Expiration ici peut être installé environ dans ~ 10 jours ou même plus (cela dépend aussi de vos considérations).

FYI: Puisque refresh tokens ont une longue durée de vie, pour les rendre vraiment sûrs, vous voudrez peut-être les stocker dans votre base de données (les demandes de rafraîchissement de jeton sont rarement effectuées). De cette façon, disons, même si votre jeton d'actualisation a été piraté d'une manière ou d'une autre et que quelqu'un a régénéré access/refresh jetons, bien sûr, vous perdrez des autorisations, mais vous pourrez toujours vous connecter au système, car vous savez vous connecter/passer (au cas où vous les utiliseriez plus tard) ou simplement en vous connectant via n'importe quel réseau social.


Où stocker ces jetons?

Il y a essentiellement 2 endroits communs:

  1. Stockage Web HTML5 (localStorage/sessionStorage)

Bon pour aller, mais en même temps assez risqué. Le stockage est accessible via du code javascript sur le même domaine. Cela signifie que si vous avez XSS , vos jetons peuvent être piratés. Donc, en choisissant cette méthode, vous devez faire attention et encoder/échapper toutes les données non fiables. Et même si vous l'avez fait, je suis presque sûr que vous utilisez un tas de modules côté client tiers et il n'y a aucune garantie qu'aucun d'entre eux ne contienne du code malveillant.

Également Web Storage n'applique aucune norme sécurisée pendant le transfert. Vous devez donc être sûr que JWT est envoyé sur HTTPS et jamais HTTP.

  1. Cookies

Avec l'option HttpOnly spécifique, les cookies ne sont pas accessibles via javascript et sont immunisés contre XSS. Vous pouvez également définir l'indicateur de cookie Secure pour garantir que le cookie est uniquement envoyé via HTTPS. Cependant, les cookies sont vulnérables à un autre type d'attaque: la falsification de requêtes intersites ( CSRF ). Dans ce cas, CSRF pourrait être empêché en utilisant une sorte de modèle de jeton synchronisé. Il y a une bonne implémentation dans AngularJS, dans Considérations sur la sécurité section.

Un article que vous voudrez peut-être suivre.

Pour illustrer son fonctionnement en général:

http://jlabusch.github.io/oauth2-server/img/diag_refresh_token.png


Quelques mots sur [~ # ~] jwt [~ # ~] lui-même:

Pour être clair, il y a vraiment du bon Débogueur JWT de la part des gars d'Auth0. Il existe 2 (parfois 3) types de revendications communs: public, private (et réservé ).

Un exemple de JWT corps (la charge utile, peut être ce que vous voulez):

{     
  name: "Dave Doe",
  isAdmin: true,
  providerToken: '...' // should be verified then separately
}

Vous trouverez plus d'informations sur la structure de JWTici .

32
oleh.meleshko

Pour répondre aux deux questions spécifiques que vous avez posées:

Quel serait le flux lorsqu'un utilisateur se connecte avec un fournisseur OAuth pour la première fois? Emberjs devrait-il envoyer tous les détails au backend à chaque connexion afin que le backend puisse ajouter de nouveaux utilisateurs au db?

Chaque fois qu'un utilisateur s'inscrit ou se connecte via oauth et que votre client reçoit un nouveau jeton d'accès, je le ferais upsert (mettre à jour ou insérer) dans votre table des utilisateurs (ou collection) avec toutes les informations nouvelles ou mises à jour que vous avez récupérées sur l'utilisateur à partir de l'API du fournisseur oauth. Je suggère de les stocker directement sur chaque enregistrement des utilisateurs pour garantir le jeton d'accès et associé les informations de profil changent de manière atomique. En général, je les composais généralement en une sorte de middleware qui effectue automatiquement ces étapes lorsqu'un nouveau jeton est présent.

Que devrait faire partie de mon corps JWT? Je pensais que l'uid et le fournisseur avaient fourni un jeton d'accès. Un problème auquel je peux penser ici est que le jeton d'accès spécifique au fournisseur peut changer. L'utilisateur peut révoquer le jeton du site du fournisseur et se réinscrire avec emberjs.

Le corps JWT se compose généralement des revendications des utilisateurs. Personnellement, je vois peu d'avantages à stocker le jeton d'accès du fournisseur dans le corps d'un jeton JWT car cela aurait peu d'avantages pour votre application client (à moins que vous ne fassiez beaucoup d'appels d'API directs de votre client à leur API, je préfère le faire ces appels côté serveur et renvoie à mon client d'application un ensemble normalisé de revendications qui adhèrent à ma propre interface). En écrivant votre propre interface de réclamation, vous n'aurez pas à contourner les différentes différences présentes entre plusieurs fournisseurs de votre application client. Un exemple de cela serait de fusionner des champs spécifiques de Twitter et Facebook qui sont nommés différemment dans leurs API aux champs communs que vous stockez sur votre table de profil utilisateur, puis d'incorporer vos champs de profil local en tant que revendications dans votre corps JWT à interpréter par votre application cliente . Il y a un avantage supplémentaire à cela: vous ne conserverez aucune donnée susceptible de fuir à l'avenir dans un jeton JWT non chiffré.

Que vous stockiez ou non le jeton d'accès fourni par le fournisseur oauth dans le corps du jeton JWT, vous devrez accorder un nouveau jeton JWT chaque fois que les données de profil changent (vous pouvez mettre en place un mécanisme pour contourner l'émission de nouveaux jetons JWT si aucune mise à jour de profil ne s'est produite et que le jeton précédent est toujours valide).

En plus des champs de profil que vous stockez en tant que revendications dans le corps de jeton JWT, je définirais toujours la norme champs de corps de jeton JWT de:

{
    iss: "https://YOUR_NAMESPACE",
    sub: "{connection}|{user_id}",
    aud: "YOUR_CLIENT_ID",
    exp: 1372674336,
    iat: 1372638336
}
5
cchamberlain

Pour tout flux de travail OAuth vous devez absolument utiliser la bibliothèque passportjs . Vous devriez également lire la documentation complète. Elle est facile à comprendre mais j'ai fait l'erreur de ne pas lire le le tout la première fois et a rencontré des difficultés. Il contient OAuth Authentification avec plus de 300 fournisseurs et jetons d'émission.

Néanmoins, si vous voulez le faire manuellement ou souhaitez une compréhension de base, voici le flux que j'utiliserais:

  1. Frontend a une page de connexion répertoriant la connexion avec Google/Facebook, etc. où OAuth est implémenté.

  2. Réussi OAuth résulte en un uid, une connexion, un access_token etc. (objet JSON)

  3. Vous POST l'objet JSON à votre route /login/ Dans votre application Node.js. (Oui, vous envoyez la réponse entière, qu'il s'agisse d'un utilisateur nouveau ou existant. Envoi de données supplémentaires c'est mieux que de faire deux requêtes)

  4. L'application backend lit le uid et le access_token. Assurez-vous que le access_token Est valide en suivant ( https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken ) ou demander des données utilisateur au fournisseur à l'aide du jeton d'accès. (Cela échouera pour les jetons d'accès non valides puisque OAuth sont générés par application/développeur). Maintenant, recherchez dans votre base de données principale.

  5. Si le uid existe dans la base de données, vous mettez à jour le access_token de l'utilisateur et expiresIn dans la base de données. (Le access_token vous permet d'obtenir plus d'informations de Facebook pour cet utilisateur particulier et il donne généralement accès pendant quelques heures.)

  6. Sinon, vous créez un nouvel utilisateur avec les informations uid, login, etc.

  7. Après avoir mis à jour le access_token ou créé un nouvel utilisateur, vous envoyez un jeton JWT contenant le uid. (Encoder le jwt avec un secret, cela garantirait qu'il a été envoyé par vous et n'a pas été falsifié. Checkout https://github.com/auth0/express-jwt )

  8. Sur le frontend après que l'utilisateur a reçu le jwt de /login, Enregistrez-le dans sessionStorage par sessionStorage.setItem('jwt', token);

  9. Sur le frontend, ajoutez également les éléments suivants:

if ($window.sessionStorage.token) { xhr.setRequestHeader("Authorization", $window.sessionStorage.token); }

Cela garantirait que s'il y a un jeton jwt, il est envoyé avec chaque demande.

  1. Sur votre fichier Node.js app.js, ajoutez

app.use(jwt({ secret: 'shhhhhhared-secret'}).unless({path: ['/login']}));

Cela validerait ce jwt pour tout ce qui se trouve sur votre chemin, garantissant que l'utilisateur est connecté, sinon il ne permettrait pas l'accès et la redirection vers la page de connexion. Le cas d'exception ici est /login Car c'est là que vous donnez à vos utilisateurs nouveaux ou non authentifiés un JWT.

Vous pouvez trouver plus d'informations sur l'URL Github sur la façon d'obtenir le jeton et de savoir quelle demande d'utilisateur vous êtes actuellement en train de servir.

3
Rahat Mahbub