Аутентификация и авторизация

Основные концепции

Аутентификация — процесс проверки личности пользователя. Обычно реализуется через логин и пароль, токены или сторонние сервисы (OAuth, OpenID).

Авторизация — определение прав доступа аутентифицированного пользователя к различным частям приложения или функциональности. В Hyperapp авторизация чаще реализуется через состояние приложения и условный рендеринг компонентов.

Hyperapp является минималистичным фреймворком для создания фронтенд-приложений с управлением состоянием через actions и state, что делает интеграцию аутентификации и авторизации легкой и гибкой.


Структура состояния для аутентификации

Состояние приложения (state) должно содержать данные пользователя и флаги статуса входа:

const state = {
  user: null,       // объект пользователя после успешной аутентификации
  token: null,      // JWT или другой токен
  isAuthenticated: false,
  loading: false,
  error: null
};
  • user — хранит информацию о текущем пользователе.
  • token — используется для авторизации запросов к серверу.
  • isAuthenticated — логический флаг состояния входа.
  • loading — индикатор процесса аутентификации.
  • error — сообщение об ошибке при неудачном входе.

Actions для аутентификации

Hyperapp использует actions для изменения состояния. Пример actions для аутентификации:

const actions = {
  login: (credentials) => async (state, actions) => {
    actions.setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });
      const data = await response.json();
      if (response.ok) {
        actions.setUser(data.user, data.token);
      } else {
        actions.setError(data.message);
      }
    } catch (err) {
      actions.setError('Сервер недоступен');
    } finally {
      actions.setLoading(false);
    }
  },
  logout: () => (state) => ({
    user: null,
    token: null,
    isAuthenticated: false,
    error: null
  }),
  setUser: (user, token) => () => ({
    user,
    token,
    isAuthenticated: true,
    error: null
  }),
  setError: (error) => () => ({ error }),
  setLoading: (loading) => () => ({ loading })
};

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

  • Асинхронные actions позволяют работать с API без сторонних библиотек.
  • Ошибки обрабатываются локально в состоянии, что упрощает отображение сообщений пользователю.
  • setUser обновляет состояние после успешной аутентификации, включая токен для авторизации последующих запросов.

Условный рендеринг компонентов

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

const view = (state, actions) => (
  <div>
    {state.isAuthenticated ? (
      <div>
        <h2>Добро пожаловать, {state.user.name}</h2>
        <button oncl ick={actions.logout}>Выйти</button>
      </div>
    ) : (
      <LoginForm login={actions.login} error={state.error} loading={state.loading}/>
    )}
  </div>
);

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

  • Отображение формы логина только если пользователь не аутентифицирован.
  • Отображение персонализированного приветствия и кнопки выхода при успешной аутентификации.
  • Использование props для передачи actions в дочерние компоненты (например, LoginForm).

Хранение токена и авторизация запросов

Токен пользователя часто хранится в localStorage или sessionStorage для сохранения сессии между перезагрузками:

actions.setUser = (user, token) => () => {
  localStorage.setItem('token', token);
  return {
    user,
    token,
    isAuthenticated: true,
    error: null
  };
};

При отправке запросов к защищённым API токен включается в заголовки:

async function fetchWithAuth(url, state) {
  return fetch(url, {
    headers: {
      'Authorization': `Bearer ${state.token}`,
      'Content-Type': 'application/json'
    }
  });
}

Роли и права доступа

Для сложных приложений состояние может содержать роли и права пользователя:

const state = {
  user: null,
  roles: [],  // ['admin', 'editor']
  isAuthenticated: false
};

Рендеринг защищённых компонентов:

const AdminPanel = (state) => (
  state.roles.includes('admin') ? <div>Панель администратора</div> : null
);

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

  • Авторизация на фронтенде является вспомогательной; сервер всегда должен проверять права.
  • Роли позволяют создавать гибкую систему условного рендеринга.

Интеграция с внешними сервисами

Hyperapp легко интегрируется с OAuth-провайдерами:

const actions = {
  oauthLogin: (provider) => async () => {
    const redirectUrl = `/auth/${provider}`;
    window.location.href = redirectUrl;
  }
};

После успешного входа через внешнего провайдера сервер возвращает токен, который сохраняется в состоянии и localStorage.


Защита маршрутов

Для SPA с несколькими страницами можно реализовать защиту маршрутов через условный рендеринг:

const ProtectedRoute = (state, children) =>
  state.isAuthenticated ? children : <div>Доступ запрещён</div>;

Использование:

<ProtectedRoute state={state}>
  <AdminPanel state={state}/>
</ProtectedRoute>

Обновление токена

Для приложений с долгими сессиями важно обновлять токен:

const actions = {
  refreshToken: async (state) => {
    try {
      const response = await fetch('/api/refresh', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${state.token}` }
      });
      const data = await response.json();
      if (response.ok) {
        return { token: data.token };
      } else {
        return { user: null, token: null, isAuthenticated: false };
      }
    } catch {
      return { user: null, token: null, isAuthenticated: false };
    }
  }
};

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

  • Позволяет поддерживать сессию без повторного входа.
  • Ошибки обновления приводят к разлогиниванию пользователя.

Эта структура обеспечивает полное управление аутентификацией и авторизацией в приложении на Hyperapp, включая хранение состояния, асинхронные действия, рендеринг защищённых компонентов, интеграцию с внешними провайдерами и обновление токена.