在使用Apollo和React捕获身份验证失败后如何正确重定向

bpy*_*ner 5 reactjs react-apollo apollo-link

我正在编写一个使用的React应用,apollo-client并且正在apollo-link-error全局捕获身份验证错误。我正在使用createBrowserHistory浏览器历史记录操纵和redux管理我的应用状态。

出现身份验证错误时,我想将用户重定向到/login页面。但是,使用history.push('/login')and forceRefresh:false可以更改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')
    }
});
Run Code Online (Sandbox Code Playgroud)

`

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

    const client = new ApolloClient({
        link: link,
        cache: new InMemoryCache(),
        connectToDevTools: true,
    });
Run Code Online (Sandbox Code Playgroud)

我认为问题是我没有使用redux-router导航方法(因此即使url更改,应用程序也保持不变)

问:当我不在组件内部时,如何获得redux history类似于使用的对象withRouter()?处理这种情况的正确方法是什么?

Vil*_*ami 5

一种可能的解决方案的简短摘要:

  1. 将所有需要身份验证的路由包装在<ProtectedRoute>将未经身份验证的用户重定向到登录页面的组件内。ProtectedRoute-组件仅检查用户是否具有有效令牌,如果没有则重定向用户。
  2. 内部错误链接首先删除令牌,或以某种方式使其无效,然后调用location.reload()

具体实现如下。

我无法找到任何直接的解决方案。在正常情况下,为了重定向用户,我使用react-router navigator()钩子。在错误链接中我发现无法使用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));
}
Run Code Online (Sandbox Code Playgroud)

我使用它如下:

<Routes>
   <Route path="login" element={<LoginPage />} />
   <ProtectedRoute path="/*" toRedirect="login">
      // protected routes here
   </ProtectedRoute>
</Routes>
Run Code Online (Sandbox Code Playgroud)

为了处理未经身份验证的用户的注销和重定向,我实现了两个函数:

// 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")
}
Run Code Online (Sandbox Code Playgroud)

最后在错误链接中:

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)
    }
);
Run Code Online (Sandbox Code Playgroud)