web-dev-qa-db-fra.com

Comment gérer les jetons d'actualisation

J'essaie de faire le tour de l'authentification basée sur JWT (JSON Web Token) pour sécuriser les points de terminaison API à l'aide de jetons d'accès. Je protège ces points de terminaison en exigeant que l'utilisateur ait un jeton d'accès valide afin d'obtenir lesdites ressources.

J'ai du mal, cependant, à comprendre l'utilisation d'un jeton d'actualisation.

J'ai un point de terminaison/auth/login qui accepte les demandes POST:

class UserLogin(Resource):
    def post(self):
        data = parser.parse_args()
        current_user = User.find_by_username(data['username'])
        if not current_user:
            return {'message': 'Unsuccessful. You do not have an account'}

        if User.verify_hash(data['password'], current_user.password):
            access_token = create_access_token(current_user)
            refresh_token = create_refresh_token(current_user)
            return {
                'message': 'Login successful {}'.format(current_user.username),
                'roles': 'Role: {}'.format(current_user.role),
                'access token': access_token,
                'refresh token': refresh_token
            }
        else:
            return {'message': 'Something went wrong'}

Le point final vérifie essentiellement les informations d'identification entrantes pour s'assurer que l'utilisateur existe dans mon magasin d'utilisateurs. S'ils le font, je retournerai un jeton d'accès et d'actualisation. Sinon, un message d'erreur à gérer côté client.

Supposons maintenant que l'utilisateur s'est connecté et tente d'accéder à une ressource privée à partir d'un autre point de terminaison sécurisé en exigeant un jeton d'accès. Le client fera une demande et le point de terminaison protégé renverra un message d'erreur indiquant qu'il n'a pas de jeton d'accès. Le client tentera alors de générer un nouveau jeton d'accès en utilisant le jeton d'actualisation obtenu lors de la connexion:

class TokenRefresh(Resource):
    @jwt_refresh_token_required
    def post(self):
        current_user = get_jwt_identity()
        access_token = create_access_token(identity=current_user)
        return {'access token': access_token}

Frapper la ressource ci-dessus générera un nouveau jeton d'accès avec un certain délai d'expiration défini. Ceci est mon flux actuel.

Ma question est, comment dois-je stocker les jetons d'actualisation pour m'assurer qu'ils sont accessibles par l'utilisateur authentifié? Dois-je avoir une logique supplémentaire dans mes demandes de connexion qui stocke le jeton d'actualisation généré pour cet utilisateur spécifique?

Si tel est le cas, dois-je alors générer un nouveau jeton d'actualisation et écraser le jeton d'actualisation existant attaché à l'utilisateur chaque fois que le client essaie d'actualiser son jeton d'accès?

À l'heure actuelle, je pense que mon processus peut être compromis si un attaquant parvient à obtenir le jeton d'actualisation.

9
Sam

Le point d'avoir des jetons d'accès est qu'ils peuvent être utilisés sans vérifier l'invalidation. Vous pouvez avoir 10000 serveurs frontaux auxquels les utilisateurs peuvent accéder avec le jeton sans avoir à demander à une base de données si elle n'est pas valide. Mais après un certain temps, le jeton expire. L'utilisateur a besoin d'un nouveau jeton d'accès, envoie son jeton d'actualisation et ce jeton d'actualisation est vérifié dans une base de données. Vous n'avez jamais besoin de vérifier les jetons d'accès expirés ou d'avoir un état, mais limitez les abus à la durée de vie du jeton.

Si vous n'avez pas l'obligation d'accepter les jetons sans vérifier l'expiration dans une base de données, vous n'avez pas besoin des deux jetons différents. Vous pouvez simplement utiliser le jeton d'actualisation pour chaque accès.

Exemple de workflow:

  1. L'utilisateur se connecte, obtient l'accès et actualise le jeton. Durée de vie du jeton d'accès 15 min, rafraîchissement du jeton 5 jours.

  2. L'utilisateur accède au service à l'aide du jeton d'accès. Le service vérifie uniquement la signature et la durée de vie. Aucune connexion à la base de données.

  3. L'utilisateur se déconnecte, le jeton d'actualisation est marqué comme expiré dans la base de données
  4. L'utilisateur accède au service en utilisant le jeton d'accès, cela fonctionne toujours
  5. Passe de 15min. Le jeton d'accès expire, l'utilisateur demande un nouveau jeton d'accès en utilisant le jeton d'actualisation toujours pendant sa durée de vie. Le service vérifie base de données et trouve que le jeton a expiré. L'utilisateur ne peut pas obtenir un nouveau jeton d'accès.

L'ensemble du système est un compromis entre le temps nécessaire pour invalider les sessions et la quantité de connexions à une source de données partagée/synchronisée requise.

Vous pouvez remplacer le jeton d'actualisation à chaque actualisation, mais n'oubliez pas que vous devez stocker tous les jetons d'actualisation expirés jusqu'à la fin de leur durée de vie.

Du point de vue de la sécurité, il est logique de créer un nouveau jeton, mais c'est un compromis entre la sécurité et la quantité de données dans votre base de données.

Dans le pire des cas (pas de durée de vie pour les jetons d'actualisation, jamais faites cela), vous devez maintenant stocker un jeton toutes les quelques minutes dans la base de données pour chaque utilisateur au lieu d'un jeton pour chaque utilisateur et vous ne pouvez jamais supprimer eux à nouveau.

Les jetons de rafraîchissement sont l'une de ces technologies où la pratique et la théorie ne correspondent pas, selon mon expérience. En théorie, vous faites une demande de connexion et récupérez un jeton d'accès (avec une courte durée de vie) et un jeton d'actualisation (qui a soit une longue période d'expiration, pas d'expiration, et peut être utilisé pour obtenir un nouveau jeton d'accès à tout moment ). Si le client essaie d'envoyer un jeton d'accès expiré et obtient un rejet du serveur, il peut envoyer le jeton d'actualisation, obtenir un nouveau jeton d'accès, puis continuer.

Dans ce cas, il est très clair que le jeton d'actualisation est vraiment puissant et doit être stocké soigneusement (par exemple dans le stockage des informations d'identification sur un appareil mobile, plutôt que dans un cookie de navigateur, ou dans un magasin côté serveur soigneusement sécurisé). Cependant, les spécifications permettent à un jeton d'actualisation d'être invalidé par le serveur d'authentification.

En pratique, il n'est donc pas rare qu'un jeton d'actualisation soit émis lors de la connexion et invalidé lors de la déconnexion ou après une période d'inactivité. Dans ce cas, il peut faire partie des données de session sur le serveur, et donc être automatiquement lié à l'utilisateur actuel. Tout autre utilisateur ne pourrait utiliser le jeton d'actualisation qu'à partir de la session authentifiée, ce qui signifie que toute logique le liant à un utilisateur spécifique échouerait également.

À partir de l'extrait de code ci-dessus, c'est à peu près ce que vous avez: le jeton d'actualisation est généré à chaque connexion, plutôt que d'être récupéré du stockage après avoir été généré lors de la création du compte, par exemple. La seule partie qui manque est dans la façon dont le jeton d'actualisation est interrogé - cela implique que le jeton d'actualisation est considéré comme suffisant pour obtenir un jeton d'accès, plutôt que la combinaison d'une session authentifiée et d'un jeton d'actualisation. C'est, comme vous le pensez, peu sûr.

Le jeton d'actualisation fournit l'autorisation d'obtenir un nouveau jeton d'accès, mais n'authentifie pas que la personne qui demande le jeton d'accès est celle qui devrait y avoir accès. Vous devez fournir l'étape d'authentification avant d'accepter l'autorisation et vous assurer qu'elle est utilisée à chaque fois que le jeton d'actualisation est utilisé - une session ouverte peut être suffisante.

Vous pouvez choisir de remplacer le jeton d'actualisation sur chaque nouveau jeton d'accès. Cela présente un avantage secondaire de se casser de manière assez évidente si plusieurs utilisateurs tentent d'utiliser le jeton d'actualisation (le second échoue), limitant ainsi l'utilisation simultanée. Évitez toutefois d'émettre de nouveaux jetons d'actualisation sans expirer l'ancien, car cela augmente le potentiel de compromission des jetons. Il présente probablement un avantage limité dans le cas où le jeton d'actualisation expire avec la session (en supposant une durée de vie de session courte), mais peut aider avec des sessions plus longues (par exemple, les fonctions "se souvenir de moi").

4
Matthew