Авторизация через Cookies в SSR с TanStack: Решение проблемы 401

#react#ssr#authentication#tanstack#cookies

TL;DR

При SSR с TanStack Query и cookie-based аутентификацией возникают 401 ошибки из-за недоступности cookies на сервере. Решение - либо передавать куки через заголовки, либо использовать гибридный подход с частичным SSR. Разберём технические детали и рабочие решения.

Введение: Проблема аутентификации в SSR

Серверный рендеринг (SSR) с механизмом аутентификации через HTTP-only cookies создаёт уникальные вызовы. В отличие от клиентского кода, где браузер автоматически прикрепляет cookies к каждому запросу, серверный рендеринг не имеет доступа к этим данным по умолчанию.

Основная боль:

// loader функция в TanStack Router
const loader = async () => {
  const res = await fetch('/api/protected'); // 401 - нет cookies!
  return res.json();
};

Основная часть: Технические решения

1. Передача cookies через заголовки

Самый чистый подход - явно передавать cookies с клиента на сервер:

// Клиентская часть (Next.js пример)
export const getServerSideProps: GetServerSideProps = async (ctx) => {
  const cookies = ctx.req.headers.cookie || '';
  
  const response = await fetch('https://api.example.com/protected', {
    headers: {
      Cookie: cookies
    }
  });
  
  // ... обработка ответа
}

2. Гибридный подход с частичным SSR

Для TanStack Query v5 можно использовать комбинацию SSR и CSR:

// Настройка QueryClient
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      ssr: false, // Отключаем SSR для авторизованных запросов
    }
  }
});

// В компоненте
const { data } = useQuery({
  queryKey: ['protected-data'],
  queryFn: fetchProtectedData,
});

3. Проксирование запросов через API роут

Альтернативное решение - создание промежуточного API эндпоинта:

// pages/api/proxy.ts
export default async function handler(req, res) {
  const response = await fetch('https://backend.example.com/protected', {
    headers: {
      Authorization: `Bearer ${req.cookies.token}`,
    },
  });
  
  res.status(response.status).json(await response.json());
}

Практическое применение: Реальный кейс

Рассмотрим реализацию для Next.js с App Router:

// app/protected/page.tsx
export default async function ProtectedPage() {
  const session = await getServerSession(); // Получаем сессию на сервере
  
  if (!session) {
    redirect('/login');
  }
  
  // Данные запрашиваем через серверный экшен
  const data = await getProtectedData(session.token);
  
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <ClientComponent />
    </HydrationBoundary>
  );
}

// Серверный экшен
async function getProtectedData(token: string) {
  const res = await fetch('https://api.example.com/protected', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });
  
  return res.json();
}

Заключение: Выбор оптимального решения

  1. Для чистого SSR - передавайте cookies через заголовки
  2. Для сложных случаев - используйте гибридный подход
  3. Для максимальной безопасности - проксируйте запросы через API роуты

Важно помнить, что HTTP-only cookies остаются самым безопасным способом хранения токенов, и их использование с SSR требует дополнительной настройки, но не является невозможным.

Код из статьи доступен в GitHub Gist: [ссылка] (замените на реальный gist)


Источник: https://www.reddit.com/r/reactjs/comments/1t026b0/cookie_based_auth_while_ssr_tanstack/