Feathers: Ajout de la prise en charge des jetons d'actualisation

Créé le 22 déc. 2015  ·  64Commentaires  ·  Source: feathersjs/feathers

Nous autorisons actuellement l'obtention d'un nouveau jeton en publiant un jeton d'authentification valide sur <loginEndpoint>/refresh . Les jetons d'actualisation ont un flux de travail légèrement différent, comme expliqué ici :
https://auth0.com/learn/refresh-tokens

Authentication Feature

Commentaire le plus utile

Cette fonctionnalité est un MUST lorsqu'il s'agit d'applications React Native. L'utilisateur se connecte au début et lorsqu'il ouvre l'application après plusieurs semaines, il s'attend à être toujours connecté.

Tous les 64 commentaires

:+1: @corymsmith et moi en parlions. En espérant aider à lancer une partie de cela sur la ligne d'arrivée pendant les "vacances".

Nous avons un support pour cela dans le master, mais aussi dans la branche decoupling . Pour rafraîchir un jeton, vous avez 2 options :

  1. Vous pouvez soit vous ré-authentifier en utilisant votre e-mail/mot de passe, Twitter, etc.
  2. Vous pouvez transmettre un jeton valide à GET /auth/token/refresh

Nous avons mis en place un processus de renouvellement des jetons, mais pas tout à fait une prise en charge complète des jetons d'actualisation comme décrit dans le lien Auth0 que j'ai posté ci-dessus. Un jeton d'actualisation réel fonctionne de la même manière qu'un code d'authentification/mot de passe GitHub, mais ne peut être utilisé que pour obtenir un nouveau jeton JWT. Ainsi, même si votre jeton JWT expire, si vous avez un jeton d'actualisation, vous pouvez l'utiliser pour vous reconnecter. Ils sont conservés dans la base de données avec l'ID utilisateur intact et peuvent être révoqués à tout moment. Du moins, c'est ce que je retiens de l'article Auth0.

Ah tu as raison @marshallswain. Je suppose que j'aurais dû cliquer sur le lien :wink:

Je pense que pour la première coupe, nous laisserons cela en dehors du jalon 1.0. Il est assez facile pour les gens de se ré-authentifier.

Je vote en quelque sorte pour que nous en fassions un feathers-authentication 2.0 chose.

Grands esprits.

Je suis toujours assez confus car on ne sait pas exactement comment fonctionne le flux de travail d'authentification.

Ce que je fais actuellement, c'est.
1.) Le client envoie le nom d'utilisateur et le mot de passe

 curl -X POST https://xxx/auth/local   -H "Content-Type: application/json"   -d '{ "email":"xxx", "password":"yyy"}'

Cela renvoie le jeton JWT.

{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjYyODUsImV4cCI6MTQ3MDQxMjY4NSwiaXNzIjoiZmVhdGhlcnMifQ.OVvQbnxfoDGxPFm3Y6tBhRae2Qa6_mDq-PVIo8RcC8Y"}

2.) Ensuite, j'ai mis ce jeton dans l'en-tête HTTP Authorizatin pour accéder à l'API.

curl -X GET https://xxx/users  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'

La chose que je ne comprends pas, c'est ensuite comment actualiser réellement ce jeton.
Ce que j'ai essayé, c'est d'envoyer ce jeton à xxx/auth/token/refresh
Ce que j'ai, c'est juste un autre jeton très long. J'ai ensuite essayé d'utiliser à la fois l'ancien et ce nouveau jeton pour accéder à l'API. les deux fonctionnent... (l'ancien ne devrait-il pas être désactivé ?)

curl -X GET https://xxx/auth/token/refresh  -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY'
{"query":{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"},"provider":"rest","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJxdWVyeSI6eyJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSJ9LCJwcm92aWRlciI6InJlc3QiLCJ0b2tlbiI6ImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpmYVdRaU9pSTFOemhoTmpVeU4yUmtNVFppTWpJd01EUmhZMlpqTm1FaUxDSnBZWFFpT2pFME56QXpNalUxTnpZc0ltVjRjQ0k2TVRRM01EUXhNVGszTml3aWFYTnpJam9pWm1WaGRHaGxjbk1pZlEuX0NIZHgzUnBFdUkxODl0OTBtWHEtSU1QWFJOdW9WaDduQndZMU9ON3hDWSIsImRhdGEiOnsiX2lkIjoiNTc4YTY1MjdkZDE2YjIyMDA0YWNmYzZhIiwiaWF0IjoxNDcwMzI1NTc2LCJleHAiOjE0NzA0MTE5NzYsImlzcyI6ImZlYXRoZXJzIiwidG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSjkuZXlKZmFXUWlPaUkxTnpoaE5qVXlOMlJrTVRaaU1qSXdNRFJoWTJaak5tRWlMQ0pwWVhRaU9qRTBOekF6TWpVMU56WXNJbVY0Y0NJNk1UUTNNRFF4TVRrM05pd2lhWE56SWpvaVptVmhkR2hsY25NaWZRLl9DSGR4M1JwRXVJMTg5dDkwbVhxLUlNUFhSTnVvVmg3bkJ3WTFPTjd4Q1kifSwiaWF0IjoxNDcwMzI2NDQyLCJleHAiOjE0NzA0MTI4NDIsImlzcyI6ImZlYXRoZXJzIn0.TqUv3051TTGbX4cPfkN-6pOOB5SN9nH-E7TU1HHSsb8","data":{"_id":"578a6527dd16b22004acfc6a","iat":1470325576,"exp":1470411976,"iss":"feathers","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NzhhNjUyN2RkMTZiMjIwMDRhY2ZjNmEiLCJpYXQiOjE0NzAzMjU1NzYsImV4cCI6MTQ3MDQxMTk3NiwiaXNzIjoiZmVhdGhlcnMifQ._CHdx3RpEuI189t90mXq-IMPXRNuoVh7nBwY1ON7xCY"}}

Des choses encore plus étranges, c'est que j'ai essayé d'utiliser ce nouveau jeton et de l'envoyer à nouveau à /auth/token/refresh.
J'ai un jeton encore plus long que celui-ci.

Je ne suis pas sûr de ce que j'ai fait de mal ou de ce que j'ai mal compris ici. Veuillez suggérer.

@parnurzeal, nous n'avons pas encore vraiment de support de jeton d'actualisation. C'est pourquoi il s'agit d'une fonctionnalité proposée.

Le moyen d'obtenir un nouveau jeton consiste à effectuer un POST sur /auth/token avec votre JWT valide existant ou à vous connecter à l'aide d'un autre mécanisme d'authentification. On dirait que vous faites tout correctement.

D'accord, mais s'il vous plaît regardez attentivement comment j'ai fait et le résultat que j'ai obtenu.

Utilisons un exemple simple.
Lorsque je demande un nouveau jeton en utilisant abcdefghijklmno (juste un jeton non-sens aléatoire).
Le retour de réponse n'est qu'une version plus longue du jeton précédent -> abcdefghijklmnopqrstuvwxyz
Si j'essaye de le refaire en utilisant abcdefghijklmnopqrstuvwxyz , j'en obtiendrai une version plus longue ->
abcdefghijklmnopqrstuvwxyz1234567890 et la boucle continue (en demandant plus, vous obtenez une version plus longue de la précédente).

De plus, les trois jetons ci-dessus sont tous utilisables en même temps.
Le jeton précédent ne devrait-il pas expirer après que nous ayons demandé un nouveau jeton ?

@parnurzeal ce que je dis, c'est de ne pas faire ce que vous avez fait car cette fonctionnalité n'est pas vraiment implémentée. Sur la base de l'implémentation (jusqu'à présent), le fait que le jeton continue de croître à chaque fois que vous atteignez /auth/token/refresh est dû au fait que nous ne faisons que réinsérer les données dans le jeton. Ce n'est pas comme cela que cela est censé fonctionner et nous n'avons pas eu le temps de le terminer et pourquoi cela n'est pas documenté. Vous n'êtes pas censé l'utiliser.

Le jeton précédent ne devrait-il pas expirer après que nous ayons demandé un nouveau jeton ?

C'est la nature de JWT. Ils expirent d'eux-mêmes à partir de leur TTL. Si vous souhaitez empêcher l'utilisation d'anciens tokens qui n'ont pas encore expiré, vous devez maintenir une liste noire. Actuellement, cela vous appartient et nous avons un problème ouvert (#133) à ce sujet, mais nous n'y arriverons probablement pas de sitôt (voire jamais).

Salut, j'ai regardé dans /auth/refresh/token et je suis sorti avec quelque chose comme ça :

...
function pick (o, ...props) {
  return Object.assign({}, ...props.map(prop => ({[prop]: o[prop]})));
}

// Provider specific config
const defaults = {
  payload: ['id', 'role'],
  passwordField: 'password',
  issuer: 'feathers',
  algorithm: 'HS256',
  expiresIn: '1d', // 1 day
};
...
// GET /auth/token/refresh
  get (id, params) {
    if (id !== 'refresh') {
      return Promise.reject(new errors.NotFound());
    }

    const options = this.options;

    // Add payload fields
    const data = pick(params.payload, options.payload);

    return new Promise(resolve => {
      jwt.sign(data, config.get('auth').token.secret, options, token => {
        return resolve({token: token});
      });
    });

  }

Est-ce trop naïf comme implémentation ? Sinon, je pourrais essayer de peaufiner, ajouter quelques tests et créer un PR.

@aboutlo merci pour l'effort ! Il est préférable d'attendre la sortie de la v0.8 (elle est en alpha depuis un certain temps maintenant) car de nombreux changements se sont produits et cet itinéraire pourrait disparaître cette semaine.
Je coupe une version bêta aujourd'hui et je termine actuellement le guide de migration. Ce ne sera donc pas long et la v0.8 résout beaucoup de problèmes actuels avec auth.

Nous avons beaucoup réfléchi à l'actualisation des jetons, donc une fois la version 0.8 publiée (cette semaine), j'aimerais discuter de ce problème. Je mettrai probablement en place nos réflexions préliminaires plus tard cette semaine.

d' accord @ekryski , j'attendrai la 0.8 :)

Cette fonctionnalité est un MUST lorsqu'il s'agit d'applications React Native. L'utilisateur se connecte au début et lorsqu'il ouvre l'application après plusieurs semaines, il s'attend à être toujours connecté.

@deiucanta la bonne nouvelle est que nous avons gardé cette fonctionnalité à l'esprit lorsque nous avons conçu [email protected]. Je ne pense pas qu'il faudra longtemps avant de le mettre en place et de le documenter.

c'est une bonne nouvelle! j'attends ça avec impatience

@marshallswain Dans l' attente d'une mise à jour sur cette fonctionnalité. S'il vous plaît laissez-moi savoir quand pouvons-nous nous attendre à cela. Ou est-il déjà sorti ? Merci d'avance.

@deiucanta En attendant, jusqu'à ce que cette fonctionnalité soit publiée, vous pouvez utiliser des jetons à plus longue durée de vie. Une fois libéré, vous pouvez faire pivoter votre secret d'authentification vers une nouvelle valeur pour effacer toutes les sessions existantes et amener tous vos utilisateurs sur les sessions les plus courtes et les plus renouvelées.

@atulrpandey ce n'est pas officiellement publié mais ce n'est pas difficile à mettre en œuvre non plus. Vous ajoutez simplement un crochet pour générer un nouveau jeton d'actualisation et le stockez sur l'objet utilisateur dans la base de données et une fois qu'il est épuisé ou expiré, vous le supprimez de l'utilisateur.

@petermikitsh une autre chose que vous pouvez faire (si vous êtes sur mobile) est de stocker un clientId et un clientSecret toute sécurité sur le client et si le JWT accessToken expire, vous vous ré-authentifiez simplement avec ceux-ci.

@ekryski pouvez-vous fournir un exemple de l'une ou l'autre de ces stratégies ? ce sera vraiment utile.
ou, faudra-t-il longtemps pour que le support officiel soit publié ? cela va vraiment aider avec l'authentification mobile !

À propos des jetons d'actualisation :

Les jetons d'actualisation contiennent les informations nécessaires pour obtenir un nouveau jeton d'accès. En d'autres termes, chaque fois qu'un jeton d'accès est requis pour accéder à une ressource spécifique, un client peut utiliser un jeton de rafraîchissement pour obtenir un nouveau jeton d'accès émis par le serveur d'authentification. Les cas d'utilisation courants incluent l'obtention de nouveaux jetons d'accès après l'expiration des anciens ou l'accès à une nouvelle ressource pour la première fois. Les jetons de rafraîchissement peuvent également expirer mais ont une durée de vie assez longue. Les jetons de rafraîchissement sont généralement soumis à des exigences de stockage strictes pour s'assurer qu'ils ne sont pas divulgués. Ils peuvent également être blacklistés par le serveur d'autorisation. - https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

Je suis donc moi-même entré dans React Native plus tôt que je ne le pensais. Je pense éventuellement contribuer à cela, mais je voudrais être sûr de bien comprendre la mécanique pour être sûr que c'est la bonne mise en œuvre. Étant donné que les jetons d'actualisation ne sont pas sans état, il y aura certaines contraintes d'utilisation (par exemple, les développeurs devront fournir un adaptateur de stockage).

Pouvez-vous utiliser un JWT valide pour obtenir un jeton d'actualisation ? Ou les jetons d'actualisation sont-ils automatiquement renvoyés dans la réponse d'authentification (par exemple, en plus du accessToken ) ? Il semble qu'Auth0 les inclue dans les réponses d'authentification (à la fois accessToken et refreshToken ) dans leur exemple sur https://auth0.com/learn/refresh-tokens/.

@petermikitsh Je ne pense pas que vous devriez pouvoir utiliser un JWT valide pour obtenir un jeton d'actualisation. Si vous faites cela, n'importe qui peut obtenir un JWT et conserver l'accès à un compte.

Les jetons d'actualisation sont généralement renvoyés avec les réponses de connexion/inscription, puis le client ne peut plus vraiment y accéder pour la session spécifique à moins qu'il ne se connecte/s'inscrive à nouveau, ce qui lui donne une nouvelle session et un nouveau jeton d'actualisation.

Les jetons d'actualisation n'ont pas vraiment besoin d'expirer, mais ils peuvent être évoqués s'ils sont stockés dans la base de données. Ainsi, l'utilisateur peut également voir le nombre de sessions actives dont il dispose. Vous pouvez stocker plus d'informations lors de l'émission de jetons d'actualisation (comme le système d'exploitation, l'adresse IP, le nom de l'appareil, etc. pour les rendre identifiables - comme le font Facebook, GitHub).

Du moins c'est comme ça que je fais.

Il devrait être possible d'utiliser un jeton d'actualisation valide pour obtenir un jeton d'actualisation tant que vous vérifiez l'autorisation à la fois lorsque vous émettez le jeton d'actualisation et lorsque vous essayez d'utiliser le jeton d'actualisation.

@marshallswain

Nous autorisons actuellement l'obtention d'un nouveau jeton en publiant un jeton d'authentification valide sur <loginEndpoint>/refresh . Les jetons d'actualisation ont un flux de travail légèrement différent ...

Alors il devrait s'appeler renew non refresh pour éviter la confusion <loginEndpoint>/renew

Comme l'a dit @abhishekbhardwaj

accessToken, ne doit pas être actualisable par un accessToken, mais uniquement par un refresehToken ou un nom d'utilisateur/mot de passe, un refreshToken ne doit être actualisable que par une authentification utilisateur/mot de passe, ou un autre secret, qui n'est pas accessible par le navigateur, comme un facteur 2 auth...

Actuellement, il est possible de rafraîchir votre accessToken avec un accessToken, comme mentionné ici :

https://github.com/feathersjs/authentication-jwt/issues/61

Une autre approche consiste à stocker le jeton d'actualisation dans la charge utile d'accessToken, puis l'API d'actualisation actuelle vérifie si le jeton d'actualisation n'est pas révoqué (via la base de données ou un appel redis). De cette façon, le jeton d'actualisation pourrait être un simple identifiant d'incrémentation automatique. De plus, l'actualisation de l'API ne devrait plus vérifier l'expiration. Étant donné que le jeton d'actualisation (entier simple) est signé avec un jeton d'accès, il est sécurisé.

De cette façon, les modifications apportées à la base de code actuelle devraient être minimes : pour l'actualisation de l'API, fournissez un moyen pour que l'utilisateur puisse fournir un crochet pour vérifier si un jeton d'accès n'est pas révoqué (en vérifiant le jeton d'actualisation dans sa charge utile), si ce crochet est fourni ne ' t valider l'heure d'expiration plus.

Pourriez-vous expliquer davantage cette approche @arash16 ?

Si vous stockez le jeton d'actualisation dans la charge utile accessTokens et que l'API d'actualisation ne vérifie pas l'expiration, n'avez-vous pas simplement rendu chaque accessToken "non expirant"

Parce que n'importe quel accessToken peut être utilisé pour obtenir un nouveau jeton d'accès, n'est-ce pas ?

Est-ce que j'ai raté quelque chose ?

@BigAB
Je voulais dire ne pas vérifier l'expiration uniquement pour l'api de rafraîchissement , le même accessToken est utilisé à la fois comme jeton de rafraîchissement et comme jeton d'accès. Ce jeton n'expire pas uniquement pour l'actualisation et l'obtention d'un nouveau jeton d'accès, l'identifiant d'actualisation lui-même peut être révoqué manuellement par l'utilisateur .

Le développeur doit avoir une table de base de données/redis pour stocker tous les identifiants de rafraîchissement. Lorsqu'un utilisateur doit révoquer ou se déconnecter de certaines (ou de toutes) autres sessions, nous pouvons lui fournir une liste de tous les identifiants de rafraîchissement (ainsi que d'autres informations supplémentaires telles que le navigateur ou la date de création, etc.) et il choisit de supprimer (signer -hors de) eux sélectivement. Après cela, une fois que le jeton contenant ces identifiants de rafraîchissement a expiré, refresh api refuse d'en donner un nouveau.

Le jeton de rafraîchissement à l'intérieur n'est pas utilisé la plupart du temps et l'autorisation est sans état jusqu'à l'expiration du jeton, après quoi nous pouvons avoir un seul appel à db pour valider l'identifiant de rafraîchissement et renvoyer un nouveau jeton d'accès.

Le délai d'expiration du jeton d'accès peut être court (moins de 10 minutes), l'utilisateur peut fermer la page et s'éloigner, plus tard, lorsqu'il ouvrira la page, le jeton d'accès a déjà expiré et il est déconnecté. Mais refresh-id à l'intérieur du jeton a une durée de vie beaucoup

Du point de vue de la sécurité, le jeton d'accès utilisé de cette manière doit être traité comme une ancienne clé de session, avec l'avantage supplémentaire que nous n'aurons pas à appeler la base de données pour le valider à chaque fois (seulement une fois expiré).

@ arash16 , j'aime votre idée de stocker le jeton d'actualisation dans le JWT d'accès. Existe-t-il un exemple, comment récupérer ce jeton d'actualisation côté serveur ?

Mon problème actuel : si le jeton d'accès a expiré, la charge utile n'est pas disponible dans le contexte du crochet de plumes. Je suppose que , d' une façon serait d'utiliser le verifyJWT() fonction d'utilité de @feathersjs/authentication forfait, par exemple dans le début de app.service('authentication').hooks({ before: { create: ... } }) ?

Existe-t-il un moyen conscient d'utiliser les jetons de rafraîchissement dans les plumes en ce moment ? Avez-vous l'intention d'ajouter un support pour eux ?

Bonjour à tous ,

On dirait qu'il n'est toujours pas disponible (ou est-ce que j'ai raté quelque chose). Pourriez-vous s'il vous plaît nous faire savoir quand il sera disponible ? Je vois qu'un référentiel de jetons d'actualisation de passeport est disponible. Quelqu'un a essayé ça ?
https://github.com/fiznool/passport-oauth2-refresh

Bonjour @daffl ,
Quelqu'un peut-il m'aider à comprendre comment le jeton peut être actualisé pour la stratégie Google ? Parce que je n'aurai pas de mot de passe pour le scénario de connexion google ?

Merci

Une autre approche consiste à stocker le jeton d'actualisation dans la charge utile d'accessToken

Ainsi, quiconque possède ne serait- même expiré pourra facilement générer un nombre infini de nouveaux tokens d'accès ou est-ce que je manque quelque chose ?

Les jetons d'actualisation doivent être stockés en toute sécurité sur le client et personne, à l' exception de ce client, ne doit y avoir accès !

@deiucanta la bonne nouvelle est que nous avons gardé cette fonctionnalité à l'esprit lorsque nous avons conçu [email protected]. Je ne pense pas qu'il faudra longtemps avant de le mettre en place et de le documenter.

Cela a été publié en 2016. Les gars, avez-vous toujours l'intention de prendre en charge cette fonctionnalité indispensable ?

Non. Vous ne pouvez rien faire avec un jeton expiré. De plus, les jetons d'actualisation sont beaucoup plus facilement possibles dans la pré-version v4 . Une entrée de livre de recettes expliquant comment le faire fera partie de la version finale.

De plus, les jetons d'actualisation sont beaucoup plus facilement possibles dans la pré-version v4 .

Y a-t-il une date de sortie approximative ?

Une entrée de livre de recettes expliquant comment le faire fera partie de la version finale.

En attendant la sortie de ce guide, pouvez-vous expliquer en quelques mots comment cela peut être fait dans la pré-version v4 ?

@daffl ping

Existe-t-il encore un moyen officiel de le faire? la v4 est là et je ne vois rien dans la doc

@daffl pouvez-vous expliquer comment cela est réalisable avec 4.0 et sans piratage du service d'authentification ?

@MichaelErmer comme solution de contournement, vous pouvez utiliser une stratégie locale ou personnalisée pour renouveler jwt, ce qui n'est pas idéal, mais fonctionne bien pour la communication interne, disons entre le travailleur et l'api.

function initAuth() {
  return async (ctx) => {
    if (ctx.path !== 'authentication') {
      const [authenticated, accessToken] = await Promise.all([
        ctx.app.get('authentication'),
        ctx.app.authentication.getAccessToken(),
      ]);

      if (!accessToken || !authenticated) {
        const result = await ctx.app.authenticate(apiLocalCreds);
        ctx.params = {
          ...ctx.params,
          ...result,
          headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
        };
      } else {
        const { exp } = decode(accessToken);
        const expired = Date.now() / 1000 > exp - 60 * 60;
        if (expired) {
          const result = await ctx.app.authenticate(apiLocalCreds);
          ctx.params = {
            ...ctx.params,
            ...result,
            headers: { ...(ctx.params.headers || {}), Authorization: result.accessToken },
          };
        }
      }
    }
    return ctx;
  };
}

client
  .configure(rest(apiHost).superagent(superagent))
  .configure(auth(authConfig))
  .hooks({ before: [initAuth()] });

Actuellement, j'utilise ce crochet after dans la v4 authentication , pour mettre à jour mon accessToken après 20 jours...

````javascript
const {DateTime} = require('luxon')
const renouvelerAprès = {jours : 20}

module.exports = () => {
retourner le contexte async => {
si (
context.method === 'créer' &&
context.type === 'après' &&
context.path === 'authentification' &&
context.data && context.data.strategy === 'jwt' &&
contexte.résultat &&
context.result.accessToken) {
// vérifie si le jeton doit être renouvelé
const payload = wait context.app.service('authentification').verifyAccessToken(context.result.accessToken)
const givenAt = DateTime.fromMillis(payload.iat * 1000)
const renouvelerAprès = issueAt.plus(renouvelerAprès)
const maintenant = DateTime.local()
if (maintenant > renouvelerAprès) {
context.result.accessToken = wait context.app.service('authentification').createAccessToken({sub: payload.sub})
}
}
contexte de retour
}
}
````

Il est important d'avoir ce hook dans after et comme dernier hook, afin que toutes les vérifications etc. soient passées

Avez-vous l'intention d'intégrer des jetons de rafraîchissement dans les plumes ?

J'appuie cette question un message plus tôt.

Je m'interroge également sur le flux de travail du jeton d'actualisation. La solution rédigée par @m0dch3n est-elle une bonne pratique ? Doit-on le mettre en œuvre autrement ?

L'ensemble du workflow refreshToken dans mon opion ne protège que très peu contre les attaques de l'homme du milieu, de sorte que si l'homme du milieu vole l'accessToken, il ne peut au moins pas le rafraîchir et avoir un accès infini aux ressources.

Il ne protège pas contre XSS, car dans ce cas, l'attaquant est capable de voler tout ce qui est stocké côté client. Donc aussi le refreshToken...

Le problème maintenant est que si vous réduisez le délai d'expiration de votre accessToken (c'est-à-dire 5 minutes), vous devez également le rafraîchir plus souvent. L'homme du milieu n'a besoin que d'écouter pendant 5 minutes les requêtes des clients afin d'intercepter le refreshToken puis... Si vous rallongez l'expiration, il a un accès plus long avec juste le accessToken...

Honnêtement, si un client me dit que son accès a été volé, je dois quand même mettre sur liste noire accessToken ET refreshToken pour être sûr. Je suis donc obligé de faire une demande de base de données sur chaque demande de toute façon.

Dans mon cas, lorsque j'ai connaissance d'un tel cas, je blackliste tous les accessTokens des 40 derniers jours, car mes accessTokens ont une validité de 40 jours...

L'utilisation de la requête HTTPS rend les attaques de l'homme du milieu vraiment difficiles. N'utilisez-vous pas des requêtes HTTPS ?

Bien sûr, j'utilise https, mais il y a 3 possibilités pour voler l'accessToken. Le premier est du côté client (XSS c'est-à-dire), le deuxième du transport (l'homme au milieu) et le troisième du côté serveur.

Sur le client et sur le transport, je ne suis qu'à moitié responsable de la sécurité, et l'autre moitié est le client, qui n'est pas totalement sous mon contrôle. Mais je peux aider le client, pour éviter les risques de sécurité, en rendant XSS impossible et en sécurisant le transport avec https...

Le but d'un refreshToken est de raccourcir l'expiration d'un accessToken ET de ne pas transmettre un jeton valide plus long ou infini à CHAQUE requête.

Donc la seule sécurité qu'il apporte, c'est qu'à partir de 100 requêtes c'est-à-dire que vous ne rendez pas toutes les 100 vulnérables sur le transport, mais seulement 1 requête

Donc, fondamentalement, une attaque d'homme au milieu ne peut pas être protégée par un refeshToken et bien sûr pas par un XSS... Il ne peut être réduit que du nombre de fois que vous transmettez ce refreshToken... Le coût de sa transmission moindre cependant, c'est que l'accessToken doit être valide plus longtemps...

Je viens de copier/coller mes commentaires de la chaîne Slack :

Je pense que le jeton d'actualisation est une fonctionnalité indispensable et qu'il ne s'agit pas de renouveler automatiquement le jeton d'accès existant. Le jeton d'accès est sans état et ne sera pas stocké côté serveur. L'inconvénient est qu'il est valable pour toujours! Plus le jeton d'accès est long, plus le risque est imposé. Mais si le jeton d'accès est trop court, vos utilisateurs doivent se connecter assez souvent, ce qui aura un impact considérable sur la convivialité.

C'est là qu'intervient le jeton d'actualisation, le jeton utilisé pour actualiser le jeton d'accès et c'est un jeton de longue durée. Lorsque le jeton d'accès a expiré, le client peut utiliser le jeton d'actualisation pour obtenir un nouveau jeton d'accès, et c'est le seul objectif du jeton d'actualisation.

Le jeton d'actualisation est révocable en cas de compromission du compte utilisateur. Et c'est la grande différence entre le jeton d'accès et le jeton d'actualisation. Pour révoquer le jeton d'actualisation émis, le serveur doit stocker tous les jetons d'actualisation émis. En d'autres termes, le jeton d'actualisation est avec état. Le serveur doit savoir lequel est valide lequel est invalide.

Pour implémenter correctement le jeton d'actualisation, nous avons besoin d'une sorte de magasin de jetons pour conserver le jeton d'actualisation. Nous devons également mettre en œuvre au moins trois flux :

Actualiser la validation du jeton
Actualiser le jeton d'accès avec un jeton d'actualisation valide
Révoquer le jeton d'actualisation de l'utilisateur compromis

Il existe d'autres fonctionnalités de gestion également intéressantes, telles que les statistiques d'utilisation des jetons.

Ci-dessus se trouve ma compréhension actuelle de la mise en œuvre du jeton d'actualisation. Ce n'est pas facile mais il est absolument nécessaire de construire un système plus sécurisé.

Il s'avère que Feathers a déjà intégré toutes les fonctionnalités/modules nécessaires pour implémenter correctement les jetons de rafraîchissement :

  1. Actualiser le magasin de jetons : peut être facilement pris en charge par Feathers Service.
  2. Émission et validation du jeton d'actualisation : peut simplement réutiliser le support JWT existant qui intègre AuthenticationService.

Sur la base du travail effectué par TheSinding (https://github.com/TheSinding/authentication-refresh-token), j'ai implémenté ma propre version de refresh-tokens avec un service personnalisé et trois hooks (https://github.com/ jackywxd/feathers-refresh-token) qui active les fonctionnalités de base des jetons de rafraîchissement :

  1. Émettez le jeton d'actualisation après l'authentification de l'utilisateur avec succès ;
  2. Actualiser le jeton d'accès avec un jeton d'actualisation JWT valide ;
  3. Déconnectez l'utilisateur en supprimant le jeton d'actualisation

Tout en tirant pleinement parti de la base de code existante dans Feathres, l'effort de codage réel est minimal et il s'intègre parfaitement à l'architecture actuelle de Feathres. Cela prouve que l'architecture actuelle de Feathers est très extensible.

Mais une fonctionnalité complète de Refresh-token nécessite également une prise en charge côté client, telle que le stockage du jeton d'actualisation côté client, la réauthentification de l'utilisateur après l'expiration du jeton d'accès, la déconnexion de l'utilisateur avec le jeton d'actualisation.

Après avoir examiné le code source de plumes-authentification et authentification-client, je pense que le jeton d'actualisation pourrait être utilisé dans le code de fonctionnalités existant pour permettre l'activation de la prise en charge du jeton d'actualisation aussi facilement que l'activation de l'authentification.

J'ai déjà porté ma base de code de jeton d'actualisation de version de hooks dans @feathersjs/authentication. Ensuite, j'essaierais de modifier l'authentification-client pour activer les fonctionnalités côté client. Mon objectif ultime est d'activer la prise en charge des jetons d'actualisation à la fois côté serveur et côté client.

Ma question/préoccupation est de savoir comment le jeton d'actualisation serait stocké dans le client ?

Voir https://auth0.com/blog/securing-single-page-applications-with-refresh-token-rotation/

Malheureusement, les RT à longue durée de vie ne conviennent pas aux SPA car il n'y a pas de mécanisme de stockage persistant dans un navigateur qui puisse garantir l'accès par l'application prévue uniquement. Comme il existe des vulnérabilités qui peuvent être exploitées pour obtenir ces artefacts de grande valeur et permettre aux acteurs malveillants d'accéder aux ressources protégées, l'utilisation de jetons d'actualisation dans les SPA a été fortement déconseillée.

Voir https://afteracademy.com/blog/implement-json-web-token-jwt-authentication-using-access-token-and-refresh-token

Alors, quel est le meilleur endroit possible pour stocker les jetons en toute sécurité ? Vous pouvez en savoir plus sur Internet si vous êtes passionné par un stockage totalement sécurisé. Certaines solutions sont idéales mais pas très pratiques. Pratiquement, je le stockerais dans les cookies avec les indicateurs httpOnly et Secure. Ce n'est pas sûr à 100 %, mais il fait le travail.

Voir cette longue discussion sur les cookies - https://github.com/feathersjs-ecosystem/authentication/issues/132 aussi

@bwgjoseph Je suggérerais d'utiliser une session express régulière et d'y stocker tous les jetons, au lieu du côté client. C'est ce que je fais et fonctionne parfaitement avec tous les types d'applications, y compris SPA

@sarkistlt Vous voulez dire de stocker tous les jetons JWT client côté serveur ? Une référence/un article pour cela ? Je ne sais pas exactement à quoi ressemblerait le processus. Alors, qu'est-ce que le client envoie lorsqu'il demande des données (CRUD) ?

@bwgjoseph comme d'

app.use('* | [or specific rout]', session(sess), (req, res, next) => {
      req.feathers.session = req.session || {};
      next();
    });

puis dans votre serveur, par exemple pour le service de connexion client, lorsque le client est authentifié, vous stockez simplement un jeton dans la session comme ctx.params.session.token = token , où jeton est votre jeton d'accès ou d'actualisation JWT, dépend de votre logique d'application.
Et avec toute nouvelle demande du client, vous vérifierez si le jeton existe dans la session et l'utiliserez pour l'authentification. C'est une approche beaucoup plus sûre et sécurisée, car aucun des jetons n'est exposé du côté client.

J'ajouterai simplement que cela fonctionne mieux pour les applications client (navigateur) - serveur. Lors de la communication en interne entre serveurs, ou travailleur/serveur, vous n'avez pas besoin de session.

Cela a été _beaucoup_ discuté auparavant (j'ai également ajouté une entrée à la FAQ ) et il n'est pas nécessairement plus sûr de stocker un jeton dans une session. Si quelqu'un a accès à votre page pour pouvoir exécuter des scripts, il a également détourné la session et peut de toute façon faire des demandes authentifiées.

Par conséquent, il est généralement acceptable de stocker un jeton dans, par exemple, localStorage (auquel seule la page actuelle a également accès) et il fonctionne également de manière transparente avec d'autres plates-formes sans navigateur (comme les applications mobiles natives, de serveur à serveur, etc.) __et websockets__ (je ne saurais trop insister sur la difficulté de faire fonctionner les websockets de manière transparente et sécurisée avec les cookies HTTP - ma vie a été beaucoup plus facile depuis que nous avons cessé d'essayer de le faire). En général, un jeton d'actualisation devrait être révocable, car il a généralement une durée de vie beaucoup plus longue.

Quoi qu'il en soit, une pull request pour cela serait la bienvenue, je trouve que cela facilite beaucoup le réglage des détails.

@jackywxd - Excellent travail,
Existe-t-il un moyen de faciliter la mise en œuvre de cela pour le développeur ?
Comme intégrer les crochets que vous avez créés dans la bibliothèque, donc nous n'aurions pas besoin d'ajouter les crochets plus tard ?

Je pense que vous devriez créer une pull request et nous pourrions avoir la discussion là-bas.

@daffl oui d'accord, surtout avec WS. Et si le client et le backend sont construits par vous ou votre équipe, oui, il est préférable d'utiliser simplement JWT et d'éviter les dépendances et la complexité supplémentaires dans votre application.
Mais dans certains cas, par exemple lors de la création d'une API REST de vitrine qui sera utilisée par des sociétés / développeurs tiers, il est plus facile d'utiliser une session régulière et de demander aux développeurs d'inclure des informations d'identification avec leurs demandes, puis de décrire comment récupérer l'accès (et actualiser ) jetons, stockez-le et transmettez-le à chaque requête. Ce qui est parfaitement géré par le client Plumes, mais dans la plupart des cas, lorsque le développeur n'est pas familiarisé avec la construction du backend, il utilisera request, superagent, fetch or axios pour connecter son application au backend. Au moins dans mon cas, c'était la raison principale pour déplacer la partie vitrine de l'API pour travailler avec des sessions régulières au lieu de JWT directement.

Mais ne serait-ce pas la décision de conception et la responsabilité du fabricant de ladite vitrine de l'implémenter dans sa propre API, puis de documenter correctement cette fonctionnalité, au lieu de "forcer" cette décision à la communauté ?

@TheSinding Je pense que nous pouvons dire "forcer" à propos de l'approche moins couramment utilisée, pas à propos des cookies qui ont été (et sont toujours) l'approche la plus couramment utilisée pour gérer les sessions utilisateur.

Et oui, c'est une décision de conception prise après les commentaires des développeurs qui ont utilisé l'API. Lorsque vous exécutez un système multi-locataire ou une API utilisé par plusieurs équipes, il est parfois préférable de suivre les pratiques générales de l'industrie pour éviter toute confusion supplémentaire et le temps supplémentaire passé par les développeurs, en particulier si une solution alternative ne présente aucun avantage pour l'utilisateur final.

Encore une fois, pour être clair, l'utilisation de JWT est géniale et beaucoup plus facile à utiliser, en particulier pour l'API en temps réel et c'est ce que nous utilisons dans 90% des cas + vous n'avez pas besoin d'exécuter redis ou autre chose pour gérer les sessions de cookies.
Mais il y a des cas exceptionnels où ce n'est peut-être pas le meilleur choix, j'ai apporté un exemple de cette situation dans mon commentaire précédent.

Je ne disais pas que tu avais tort, je pensais juste "à voix haute" :)

OMI, je pense que l'approche avec JWT serait une meilleure approche, car c'est ce qui est déjà pris en charge et en tant que @daffl, il est également plus facile de travailler avec

Merci pour tous vos commentaires! JWT vs session est un sujet différent que nous pouvons discuter séparément.

Je pense qu'une chose sur laquelle nous sommes tous d'accord est que le jeton d'accès + le jeton d'actualisation est une solution beaucoup plus sécurisée qu'un simple jeton d'accès. Il a été largement adopté par les grands géants de l'Internet. Il est juste de dire que la communauté Feathers aimerait voir la base de code principale de Feathers fournir une prise en charge intégrée du jeton d'actualisation. En fait, cela m'a surpris lorsque j'ai réalisé pour la première fois que Feathers ne prend pas en charge le jeton d'actualisation.

J'ai utilisé AWS cognito dans quelques-uns de mes projets, AWS Amplify enregistrera trois jetons dans localStorage : jeton d'identification, jeton d'accès et jeton d'actualisation, Amplify gère tous les travaux sales liés à la gestion des jetons et fournit quelques API qui permettent des interface simple fonctionnant avec le backend de Cognito. J'aimerais voir une expérience de développement similaire avec Feathers.

@TheSinding Merci pour votre excellent travail précédent !

Étant donné que l'authentification existante est implémentée en tant que service normal, nous pourrions facilement activer la prise en charge du jeton d'actualisation en étendant la classe et en ajoutant quelques crochets :

this.hooks({ after: { create: [issueRefreshToken(), connection('login'), event('login')], remove: [logoutUser(), connection('logout'), event('logout')], patch: [refreshAccessToken()], },

Tout comme nous activons l'authentification à l'aide de la CLI, nous pourrions proposer l'option similaire dans la CLI pour la prise en charge du jeton d'actualisation. le développeur peut simplement répondre OUI, l'interface de ligne de commande crée alors automatiquement un service client de « refresh-tokens » et met à jour les configurations liées aux jetons de rafraîchissement dans le fichier de configuration par défaut. C'est donc comme une solution clé en main pour les développeurs.

@daffl David, merci d'avoir créé Feathers ! Je me demandais simplement s'il y avait un "guide de contribution", une "ligne directrice de codage", un "guide de style" pour Feathers ?

Comme je l'ai mentionné précédemment, un PR avec la mise en œuvre de jetons de rafraîchissement (ou même quelques idées) serait le bienvenu. Je n'ai pas encore eu l'occasion de consulter les dépôts liés et cette discussion devient assez longue. Avoir tout à jour et en un seul endroit rendrait les choses beaucoup plus faciles. Quelques points importants :

  • Des jetons d'actualisation peuvent être émis en étendant le service d'authentification
  • Les jetons d'actualisation doivent être stockés dans localStorage
  • Les jetons d'actualisation doivent être révocables (il doit donc y avoir une implémentation plus générale du mécanisme de révocation décrit dans https://docs.feathersjs.com/cookbook/authentication/revoke-jwt.html)
  • En raison de la complexité et de la configuration supplémentaires (comme Redis pour le stockage des jetons révoqués), il peut être disponible en tant que fonctionnalité mais ne devrait pas être activé par défaut (c'est-à-dire une application générée standard - peut faire partie de la CLI mais avant de faire quoi que ce soit à ce sujet, Je cherche toujours vraiment de l'aide pour démarrer un générateur basé sur l' hygiène )
  • Les informations de contribution peuvent être trouvées dans le guide des contributeurs

Si quelqu'un utilise refreshToken, nous créons une bibliothèque qui gère en frontend, accessToken et refreshToken comme des "sessions", trop faciles à utiliser.

Il suffit de transmettre les jetons et la bibliothèque essaie d'obtenir un nouveau acessToken avec refreshToken lorsqu'il expirera. Est actuellement utilisé dans Videsk .

Référentiel : gestionnaire d'authentification frontale

Cette page vous a été utile?
0 / 5 - 0 notes