Apollo と React で認証失敗をキャッチした後に正しくリダイレ​​クトする方法 質問する

Apollo と React で認証失敗をキャッチした後に正しくリダイレ​​クトする方法 質問する

私は を使用する React アプリを作成しておりapollo-client、 を使用してapollo-link-error認証エラーをグローバルにキャッチしています。 はcreateBrowserHistoryブラウザ履歴の操作とreduxアプリの状態の管理に使用しています。

認証エラーが発生した場合、ユーザーを/loginページにリダイレクトしたいのですが、forceRefresh:false を使用してリダイレクトするhistory.push('/login')と URL は変更されますが、実際にはアプリ内を移動しません。

使用するとforceRefresh:true動作しますが、アプリが完全に再起動されるため、これを避けたいと思います。

const errorLink = onError(({ graphQLErrors, networkError }) => {
    if(graphQLErrors[0].extensions.code == "UNAUTHENTICATED") {
        // with forceRefresh:true this works, but causes a nasty 
        // reload, without the app doesn't navigate, only the url changes 
        history.push('/login')
    }
});

`

let links = [errorLink, authLink, httpLink];    
const link = ApolloLink.from(links);

    const client = new ApolloClient({
        link: link,
        cache: new InMemoryCache(),
        connectToDevTools: true,
    });

問題は、redux-routerナビゲートするためのメソッドを使用していないことだと思います(URLが変更されてもアプリは同じままです)

Q:コンポーネント内にいないときに、 をredux history使用した場合と同様のオブジェクトを取得するにはどうすればよいですか? この状況に対処する適切な方法は何ですか?withRouter()

ベストアンサー1

考えられる解決策の 1 つを簡単にまとめると次のようになります。

  1. 認証を必要とするすべてのルートを、<ProtectedRoute>認証されていないユーザーをログイン ページにリダイレクトするコンポーネント内にラップします。ProtectedRoute コンポーネントは、ユーザーが有効なトークンを持っているかどうかを確認し、持っていない場合はユーザーをリダイレクトします。
  2. エラーリンク内では、まずトークンを削除するか、何らかの方法でトークンを検証解除してから、location.reload()

詳細な実装は以下をご覧ください。

簡単な解決策は見つかりませんでした。通常の場合、ユーザーをリダイレクトするには react-router の navigation() フックを使用します。エラー リンク内では react-hooks を使用する方法が見つかりませんでした。

しかし、実際の問題は解決できました。認証を必要とするアプリケーションのすべての部分をラップする ProtectedRoute コンポーネントを実装しました。

type ProtectedRouteProps = {
    path: string;
    toRedirect: string;
};

export const ProtectedRoute: FunctionComponent<ProtectedRouteProps> = ({
    path,
    toRedirect,
    children,
}) => {
    return isAuthenticated() ? (
        <Route path={path}>
            {children}
        </Route>
    ) : (
        <Navigate to={{ pathname: toRedirect }} />
    );
};

type ValidToken = string;
type ExpiredToken = 'Expired token'
type NullToken = '' | null

export type JwtTokenType = (ValidToken | ExpiredToken | NullToken )
export const isNullToken = (token: JwtTokenType) : boolean => {
    return (token === '' || token === null)
}

export const isExpiredToken = (token: JwtTokenType) : boolean => {
    return token === "Expired token"
}

export const isAuthenticated = () : boolean => {
    let token = getTokenFromCookies();
    return !(isExpiredToken(token) || isNullToken(token));
}

以下のように使用します。

<Routes>
   <Route path="login" element={<LoginPage />} />
   <ProtectedRoute path="/*" toRedirect="login">
      // protected routes here
   </ProtectedRoute>
</Routes>

認証されていないユーザーのログアウトとリダイレクトを処理するために、次の 2 つの関数を実装しました。

// Use this in normal cases
export function useHandleLogout(): () => void {
    const navigate = useNavigate();
    // maybe call other hooks
    });
    function handleLogout() {
        navigate("/login");
        removeToken();
        // do other stuff you want
    }
    return handleLogout;
}

// Use this inside error-link
export const handleLogoutWithoutHook = () => {
    // Logout without hook
    removeToken();
    // do other stuff required when logout
    // eslint-disable-next-line no-restricted-globals
    location.reload();
    // location.reload() after token removed affects user redirect
    // when component is wrapped inside <ProtectedRoute> component

};

export const removeToken = () => {
    Cookies.remove("jwt-token")
}

そして最後に、エラー リンク内:

export const errorLink =  onError(
    ({ graphQLErrors, networkError, operation, forward }) => {
        if (graphQLErrors) {
            for (let err of graphQLErrors) {
                if (err.message.includes('AnonymousUser')) {
                    handleLogoutWithoutHook()
                    return
                }
                if (err.message.includes('Signature has expired')) {
                    handleLogoutWithoutHook()
                }
                console.log(err.message)
            }
        }
        return forward(operation)
    }
);

おすすめ記事