Как в Nuxt 3 перейти на страницу входа, если какая-либо конечная точка REST возвращает 401?

Я использую Nuxt 3 с рендерингом на стороне сервера. Для запросов REST я использую Axios.

Когда пользователь входит в систему, в браузере сохраняется файл cookie токена обновления. Затем для всех запросов к любой из конечных точек REST в заголовок включается токен доступа. Если токен доступа не найден, он запрашивается с использованием токена обновления. Если срок действия токена доступа истек, запрашивается новый, и неудачный запрос отправляется снова. Всем этим занимаются перехватчики Axios.

Теперь проблема, с которой я столкнулся, заключается в том, что если токен обновления больше не действителен, создать новый токен доступа вообще невозможно. Таким образом, конечная точка токена доступа возвращает код 401. Но когда я пытаюсь перехватить этот код 401 в своих перехватчиках Axios и перейти на страницу входа в систему, это произойдет только на стороне клиента.

Чтобы устранить проблему, я создал эту простую страницу. Он по-прежнему использует Axios, но перехватчиков нет. Основная проблема, похоже, та же самая: навигация на стороне сервера не может осуществляться из цепочки обещаний.

<template>
  <div>
    {{ data }}
    {{ error }}
  </div>
</template>

<script setup lang = "ts">
const userService = useUserService();
const { data, error } = await useAsyncData('data', () => {
  return userService.getSelf().catch(() => {
    try {
      console.info('handling error');
      navigateTo('/login');
      console.info('navigated to /login');
    } catch (ex) {
      console.info(ex);
    }
  })
});
</script>

В журнале на стороне сервера написано следующее:

handling error

WARN  [nuxt] useAsyncData should return a value that is not null or undefined or the request may be duplicated on the client side.

[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables.

Таким образом, очевидно, что NavigationTo терпит неудачу с исключением, поскольку он никогда не регистрирует «переход в / вход». В результате этого страница отображается. Страница, к которой у вас больше нет доступа.

На стороне клиента журналы говорят следующее:

GET http://127.0.0.1:8080/users/self 401 (Unauthorized)
restricted.vue:13 handling error
restricted.vue:15 navigated to /login

Таким образом, после рендеринга страницы /restricted и повторной отправки запроса к конечной точке REST на стороне клиента переход к /login успешен, и вы будете перенаправлены на /login.

Теперь, если я просто проверю, установлен ли объект данных, и, если нет, перейду к /login, все будет работать нормально. Он вернет статус 302 по запросу /restricted без предварительного отображения этой страницы. Именно так, как мне хотелось бы. Этот код выглядит следующим образом:

<template>
  <div>
    {{ data }}
    {{ error }}
  </div>
</template>

<script setup lang = "ts">
const userService = useUserService();
const { data, error } = await useAsyncData('data', () => {
  return userService.getSelf()
});
if (!data.value) {
  navigateTo('/login')
}
</script>

Конечно, это не лучший способ справиться с этим, поскольку токен обновления может стать недействительным в любой момент, на любой из сотен страниц и во время любого из сотен различных вызовов конечной точки REST. Мне нужно какое-то централизованное управление всем этим.

Что мне не хватает? Есть ли лучшие способы справиться с чем-то подобным?

В Nuxt3 вы можете использовать useFetch придумать Nuxt3 вместо axios. Чтобы перенаправить на страницу входа, используйте завернутый useFetch вместо необработанного useFetch, вот ответ и пример, который может помочь.

Ginger 04.07.2024 14:46
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
111
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я предлагаю создать собственный компонуемый объект, который можно будет использовать вместо встроенного в Nuxt useFetch или useAsyncData (или Axios, если уж на то пошло, поскольку он дает вам гораздо меньше возможностей по сравнению с useFetch в Nuxt). Внутри этого компонуемого объекта вы просто оборачиваете встроенный useFetch своей бизнес-логикой. Примерно так (кредит https://github.com/nuxt/nuxt/discussions/16715#discussioncomment-6564177):

// create useCustomFetch to replace useFetch
import { withQuery } from 'ufo'
import { defu } from 'defu'
import type { FetchError } from 'ofetch'
import type { AvailableRouterMethod, NitroFetchRequest } from 'nitropack'
import type { AsyncData, UseFetchOptions, FetchResult } from 'nuxt/app'

type PickFrom<T, K extends Array<string>> = T extends Array<any> ? T : T extends Record<string, any> ? keyof T extends K[number] ? T : K[number] extends never ? T : Pick<T, K[number]> : T;
type KeysOf<T> = Array<T extends T ? (keyof T extends string ? keyof T : never) : never>;

export function useCustomFetch<
  ResT = void,
  ErrorT = FetchError,
  ReqT extends NitroFetchRequest = NitroFetchRequest,
  Method extends AvailableRouterMethod<ReqT> = ResT extends void
    ? 'get' extends AvailableRouterMethod<ReqT>
      ? 'get'
      : AvailableRouterMethod<ReqT>
    : AvailableRouterMethod<ReqT>,
  _ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
  DataT = _ResT,
  PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
  DefaultT = null,
>(
  request: Ref<ReqT> | ReqT | (() => ReqT),
  opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
): AsyncData<PickFrom<DataT, PickKeys> | DefaultT, ErrorT | null> {
  const route = useRoute()
  const router = useRouter()
  const config = useRuntimeConfig()
  const { token, logout } = useUserSession()

  return useFetch(
    request,
    defu(
      <UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>>{
        baseURL: config.api.baseUrl,
        headers: {
          authorization: `bearer ${token.value}`,
        },
        onRequestError: (error) => {
          if (error.response?.status === 401) {
            logout()
            router.push(withQuery('/auth', { redirect: route.fullPath }))
          }
        },
      },
      opts,
    ),
  )
}

Вы можете изменить это в соответствии с вашей собственной бизнес-логикой. Просто помните, что во всем вашем приложении вы должны использовать этот собственный компонуемый объект вместо метода useFetch по умолчанию.

Ответ принят как подходящий

Вместо этого я сделал что-то вроде этого:

const userService = useUserService();
const { data, error } = await useAsyncData('data', () => {
  return userService.getSelf().catch(() => {
    if (import.meta.server) {
      const response = requestEvent.node.res;
      if (!response.headersSent) {
        response.writeHead(302, {Location: '/login'});
        response.end();
      } else {
        router.push('/login');
      }
    }
  })
});

При этом я получаю правильный ответ 302 и перенаправляюсь на страницу входа, если первоначальный запрос, обработанный на стороне сервера, не проходит аутентификацию.

Другие вопросы по теме

Похожие вопросы

Файл cookie только для HTTP в заголовке ответа от серверной части, но не сохраняется в клиентском браузере
Токен JWT с API-шлюзом постоянно возвращает несанкционированный доступ
Ошибка 404 при отправке сообщения шаблона WhatsApp с переменной в Node.js
Получение «401 Unauthorized» при доступе к защищенному маршруту в Express.js с помощью экспресс-сессии и паспорта
Как применить постоянный плагин alpinejs к зависимому раскрывающемуся списку
Reactjs вызывает несколько сообщений Axios в одной асинхронной функции
Как отменить запрос Axios в useEffect при изменении маршрута в Next.js?
Django, React, Axios возвращают html вместо данных json
Почему PWA на основе реагирования, размещенное в моей службе приложений Azure, случайно начинает возвращать ошибку 500 для ВСЕХ вызовов POST (GET работает нормально)? Исправлено: Перезапустить браузер?
Почтовый вызов Axios работает, но другой код не запускается, состояние не обновляется