Редиректы для авторизации

При построении одностраничных приложений (SPA) часто возникает необходимость управлять доступом пользователей к определённым маршрутам. Библиотека Page.js предоставляет удобный механизм для организации редиректов на основе состояния авторизации.


Настройка маршрутов с проверкой авторизации

Для начала создаётся базовая конфигурация маршрутов:

import page from 'page';

function isAuthenticated() {
  return Boolean(localStorage.getItem('token'));
}

page('/', () => {
  console.log('Главная страница');
});

page('/login', () => {
  console.log('Страница логина');
});

page('/dashboard', (ctx, next) => {
  if (!isAuthenticated()) {
    page.redirect('/login');
  } else {
    console.log('Панель пользователя');
  }
});

Ключевые моменты:

  • page.redirect('/login') — перенаправляет пользователя на указанный маршрут.
  • Проверка состояния авторизации (isAuthenticated()) должна быть лёгкой и быстрой, чтобы не блокировать рендеринг.

Использование промежуточных функций (middleware)

Page.js поддерживает концепцию middleware, которая позволяет вставлять проверки перед обработкой маршрута.

function requireAuth(ctx, next) {
  if (!isAuthenticated()) {
    page.redirect('/login');
  } else {
    next();
  }
}

page('/dashboard', requireAuth, () => {
  console.log('Панель пользователя');
});

page('/settings', requireAuth, () => {
  console.log('Настройки пользователя');
});

Преимущества middleware:

  • Повторное использование логики проверки авторизации.
  • Чистота кода — маршруты остаются читаемыми.
  • Возможность объединять несколько middleware для одной страницы (например, проверка роли пользователя и состояния авторизации).

Редирект после успешного логина

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

let redirectAfterLogin = '/';

page('/dashboard', (ctx, next) => {
  if (!isAuthenticated()) {
    redirectAfterLogin = ctx.path;
    page.redirect('/login');
  } else {
    next();
  }
});

page('/login', (ctx) => {
  console.log('Форма логина');
  // После успешного ввода
  const loginButton = document.querySelector('#login-btn');
  loginButton.addEventListener('click', () => {
    localStorage.setItem('token', 'example-token');
    page.redirect(redirectAfterLogin);
  });
});

Особенности:

  • ctx.path содержит текущий URL, на который пытался попасть пользователь.
  • Сохранение пути позволяет возвращать пользователя именно на ту страницу, с которой он начинал.

Обработка публичных и приватных маршрутов

Разделение маршрутов на публичные и приватные повышает читаемость приложения:

const publicRoutes = ['/login', '/register'];
const privateRoutes = ['/dashboard', '/settings'];

page('*', (ctx, next) => {
  if (!isAuthenticated() && privateRoutes.includes(ctx.path)) {
    page.redirect('/login');
  } else if (isAuthenticated() && publicRoutes.includes(ctx.path)) {
    page.redirect('/dashboard');
  } else {
    next();
  }
});

Выигрыш от такого подхода:

  • Глобальная обработка авторизации для всех маршрутов.
  • Минимизация дублирования кода.
  • Предотвращение доступа авторизованных пользователей к страницам логина или регистрации.

Редирект с параметрами и query string

Page.js позволяет сохранять параметры маршрута и query string при редиректе:

page('/dashboard/:section', requireAuth, (ctx) => {
  console.log('Текущий раздел:', ctx.params.section);
});

page('/login', (ctx) => {
  const redirectTo = ctx.query.redirect || '/dashboard';
  document.querySelector('#login-btn').addEventListener('click', () => {
    localStorage.setItem('token', 'example-token');
    page.redirect(redirectTo);
  });
});

Особенности использования параметров:

  • ctx.params — содержит динамические сегменты маршрута.
  • ctx.query — позволяет работать с query string и передавать дополнительные данные через URL.

Совмещение с асинхронной проверкой авторизации

Иногда проверка авторизации требует запроса к серверу:

async function checkAuthServerSide() {
  const token = localStorage.getItem('token');
  if (!token) return false;

  const response = await fetch('/api/validate-token', {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  return response.ok;
}

page('/dashboard', async (ctx, next) => {
  const authenticated = await checkAuthServerSide();
  if (!authenticated) {
    page.redirect('/login');
  } else {
    next();
  }
});

Важные моменты:

  • Middleware может быть асинхронным.
  • Использование await позволяет дождаться результата проверки перед рендерингом маршрута.
  • Асинхронная проверка предотвращает несанкционированный доступ даже при подделке токена на клиенте.

Итоговая схема редиректов

  • Приватные маршруты всегда проверяют авторизацию.
  • Публичные маршруты перенаправляют авторизованных пользователей на защищённые страницы.
  • Использование middleware и глобальной проверки упрощает поддержку приложения.
  • Сохранение пути редиректа и работа с параметрами позволяет сделать пользовательский опыт плавным.
  • Асинхронные проверки на сервере повышают безопасность.

Такая структура редиректов обеспечивает гибкий и безопасный контроль доступа в SPA с Page.js.