Как правильно перенаправить после перехвата аутентификации с помощью Apollo и React
Я пишу приложение реакции, которое использует apollo-client
и я использую apollo-link-error
ловить ошибки аутентификации глобально. я использую createBrowserHistory
для манипулирования историей браузера и redux
управлять состоянием моего приложения.
При ошибке аутентификации я хочу перенаправить пользователя на /login
стр. Тем не менее, делая это с history.push('/login')
и 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')
}
});
`
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()
когда я не внутри компонента? Как правильно справиться с этой ситуацией?
0 ответов
Краткое изложение одного из возможных решений:
- Оберните все маршруты, требующие аутентификации, внутрь
<ProtectedRoute>
компонент, который перенаправляет неаутентифицированных пользователей на страницу входа. Компонент ProtectedRoute просто проверяет, есть ли у пользователя действующий токен, если нет, перенаправляет пользователя. - Ссылка на внутреннюю ошибку сначала удалите токен или как-нибудь аннулируйте его, затем вызовите
location.reload()
Подробная реализация ниже.
Я не смог найти однозначного решения. В обычных случаях для перенаправления пользователя я использую перехватчик navigate(). Внутри ссылки на ошибку я не нашел способа использовать перехватчики реакции.
Однако реальную проблему мне удалось решить. Я реализовал компонент 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>
Для обработки выхода из системы и перенаправления для неаутентифицированного пользователя я реализовал две функции:
// 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)
}
);