web-dev-qa-db-fra.com

Comment implémenter le jeton d'actualisation automatique dans graphql pour l'authentification basée sur JWT?

J'essaie de comprendre ce scénario pour mon authentification basée sur JWT dans le serveur graphql basé sur Apollo (2.0).

Fondamentalement, après la connexion, un utilisateur obtient accessToken et refreshToken à partir du serveur.

AccessToken arrive à expiration après une certaine période de temps et le serveur envoie un message d'erreur indiquant que le jeton a expiré (TokenExpiredError), puis le client doit communiquer avec le serveur pour un nouveau accessToken via la transmission de refreshToken.

Le flux est le suivant -

  1. TokenExpiredError se produit
  2. Obtenez cette erreur côté client
  3. Mettez toutes les demandes en file d'attente avec l'ancien accessToken (afin que le serveur ne soit pas inondé avec trop d'appels refreshToken et que de nombreux accessTokens soient générés par le serveur)
  4. Appelez refreshToken api sur le serveur graphql pour obtenir un nouvel accessToken
  5. mettre à jour accessToken pour tous les appels autorisés avec un nouvel accessToken
  6. L'utilisateur déconnecté incase refreshToken lui-même est expiré
  7. Empêcher tout type de condition de concurrence appels n/b

J'ai déjà implémenté la mutation refreshToken côté client, mais je ne peux pas savoir quand l'erreur se produit, arrêtez toutes les demandes -> demandez un nouveau jeton -> refaites toutes les demandes en attente et si le jeton d'actualisation est expiré, déconnectez-vous.

10
WitVault

J'ai suivi cette approche pour résoudre enfin mon problème

Publier mon approche pour les autres

// @flow
import { ApolloLink, Observable } from 'apollo-link';
import type { ApolloClient } from 'apollo-client';
import type { Operation, NextLink } from 'apollo-link';

import { refreshToken2, getToken } from './token-service';
import { GraphQLError } from 'graphql';

export class AuthLink extends ApolloLink {
     tokenRefreshingPromise: Promise<boolean> | null;

injectClient = (client: ApolloClient): void => {
    this.client = client;
};

refreshToken = (): Promise<boolean> => {
    //if (!this.tokenRefreshingPromise) this.tokenRefreshingPromise = refreshToken(this.client);
    if (!this.tokenRefreshingPromise) this.tokenRefreshingPromise = refreshToken2();
    return this.tokenRefreshingPromise;
};

setTokenHeader = (operation: Operation): void => {
    const token = getToken();
    if (token) operation.setContext({ headers: { authorization: `Bearer ${token}` } });
};

request(operation: Operation, forward: NextLink) {
    // set token in header
    this.setTokenHeader(operation);
    // try refreshing token once if it has expired
    return new Observable(observer => {
        let subscription, innerSubscription, inner2Subscription;
        try {
            subscription = forward(operation).subscribe({
                next: result => {
                    if (result.errors) {
                        console.log("---->", JSON.stringify(result.errors))
                        for (let err of result.errors) {
                            switch (err.extensions.code) {
                              case 'E140':
                                console.log('E140', result)
                                observer.error(result.errors)
                                break;
                              case 'G130':
                                    this.refreshToken().then(response => {
                                        if (response.data && !response.errors) {
                                            this.setTokenHeader(operation);
                                            innerSubscription = forward(operation).subscribe(observer);
                                        } else {
                                            console.log("After refresh token", JSON.stringify(response));
                                            observer.next(response)
                                        }
                                    }).catch(console.log);
                                break;
                            }
                          }
                    } 
                    observer.next(result)

                  },
                complete: observer.complete.bind(observer),
                error: netowrkError => {
                    observer.error(netowrkError);
                  }
                },
            });
        } catch (e) {
            observer.error(e);
        }
        return () => {
            if (subscription) subscription.unsubscribe();
            if (innerSubscription) innerSubscription.unsubscribe();
            if (inner2Subscription) inner2Subscription.unsubscribe();
        };
    });
}
}
1
WitVault