web-dev-qa-db-ja.com

jwtベースの認証のためにgraphqlに自動更新トークンを実装する方法は?

ApolloベースのgraphqlサーバーでのJWTベースの認証のこのシナリオを理解しようとしています(2.0).

基本的に、ログイン後、ユーザーはサーバーからaccessTokenとrefreshTokenを取得します。

AccessTokenは一定期間後に期限切れになり、サーバーはトークンが期限切れであることを示すエラーメッセージを送信し(TokenExpiredError)、クライアントはrefreshTokenを渡すことで新しいaccessTokenのサーバーと通信する必要があります。

フローは次のとおりです-

  1. TokenExpiredErrorが発生
  2. クライアント側でそのエラーを取得する
  3. 古いaccessTokenを使用してすべての要求をキューに入れます(サーバーが大量のrefreshToken呼び出しであふれないようにし、多くのaccessTokenがサーバーによって生成されます)
  4. GraphqlサーバーでrefreshToken APIを呼び出して、新しいaccessTokenを取得します
  5. 新しいaccessTokenを使用して、すべての承認された呼び出しのaccessTokenを更新します
  6. ログアウトユーザーがrefreshToken自体を期限切れにする場合
  7. あらゆる種類の競合状態の白黒呼び出しを防ぐ

クライアント側にrefreshTokenミューテーションをすでに実装していますが、エラーがいつ発生するのかわかりません-すべてのリクエストを停止->新しいトークンをリクエスト->すべての保留中のリクエストを再度作成し、リフレッシュトークンが期限切れの場合はログアウトユーザー。

10
WitVault

this アプローチに従って問題を解決しました

アプローチを他の人に投稿する

// @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