Аутентификация в компонентах

Основы управления состоянием аутентификации

В приложениях на Lit аутентификация часто реализуется через хранение состояния пользователя в глобальном или локальном хранилище. Основными подходами являются:

  • Состояние через реактивные свойства компонента: Lit позволяет объявлять свойства с помощью декоратора @property. Эти свойства автоматически отслеживают изменения и обновляют DOM, что удобно для отображения интерфейса в зависимости от состояния аутентификации.
import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';

class UserPanel extends LitElement {
  @property({ type: Boolean })
  loggedIn = false;

  render() {
    return html`
      ${this.loggedIn ? html`<p>Добро пожаловать, пользователь!</p>` : html`<p>Пожалуйста, войдите</p>`}
    `;
  }
}

customElements.define('user-panel', UserPanel);
  • Глобальное состояние через хранилища: Для крупных приложений чаще используют внешние хранилища состояния, такие как Redux, MobX или простые EventEmitter-паттерны. Компоненты Lit подписываются на изменения состояния и перерисовываются при его обновлении.
import { LitElement, html } from 'lit';
import { store } from './store.js';

class AuthStatus extends LitElement {
  connectedCallback() {
    super.connectedCallback();
    store.subscribe(() => this.requestUpdate());
  }

  render() {
    const { user } = store.getState();
    return html`${user ? html`<p>Привет, ${user.name}</p>` : html`<p>Вы не вошли</p>`}`;
  }
}

customElements.define('auth-status', AuthStatus);

Интеграция с токенами

Аутентификация в современных веб-приложениях обычно строится на использовании JWT или других токенов. В Lit токен можно хранить в localStorage или sessionStorage, а при создании компонента проверять его наличие и валидность.

class AuthCheck extends LitElement {
  @property({ type: Boolean })
  authenticated = false;

  connectedCallback() {
    super.connectedCallback();
    const token = localStorage.getItem('authToken');
    this.authenticated = !!token;
  }

  render() {
    return html`
      ${this.authenticated ? html`<p>Доступ разрешён</p>` : html`<p>Доступ запрещён</p>`}
    `;
  }
}

customElements.define('auth-check', AuthCheck);

Управление маршрутами и защитой страниц

Для SPA на Lit часто используется комбинация с роутерами, такими как @vaadin/router или lit-router. Защита маршрутов строится через проверку состояния аутентификации перед рендером компонента.

import { Router } from '@vaadin/router';

const router = new Router(document.querySelector('#outlet'));
router.setRoutes([
  { path: '/', component: 'home-page' },
  { 
    path: '/dashboard', 
    component: 'dashboard-page', 
    action: async (context, commands) => {
      const token = localStorage.getItem('authToken');
      if (!token) return commands.redirect('/');
      return undefined;
    } 
  }
]);

Обработка входа и выхода

Компоненты Lit могут предоставлять кнопки и формы для логина и логаута, обновляя реактивные свойства и хранилища. Важным моментом является корректная синхронизация состояния и локального хранилища.

class LoginForm extends LitElement {
  @property({ type: Boolean })
  loggedIn = false;

  handleLogin() {
    // эмуляция получения токена от сервера
    const token = 'sample-jwt-token';
    localStorage.setItem('authToken', token);
    this.loggedIn = true;
  }

  handleLogout() {
    localStorage.removeItem('authToken');
    this.loggedIn = false;
  }

  render() {
    return html`
      ${this.loggedIn 
        ? html`<button @click=${this.handleLogout}>Выйти</button>` 
        : html`<button @click=${this.handleLogin}>Войти</button>`}
    `;
  }
}

customElements.define('login-form', LoginForm);

Реактивная синхронизация нескольких компонентов

Если в приложении несколько компонентов зависят от состояния аутентификации, важно использовать централизованное хранилище или событие кастомного типа для уведомления всех компонентов:

// Dispatcher
export function authChanged(isLoggedIn) {
  window.dispatchEvent(new CustomEvent('auth-changed', { detail: isLoggedIn }));
}

// Listener
class HeaderComponent extends LitElement {
  @property({ type: Boolean }) loggedIn = false;

  connectedCallback() {
    super.connectedCallback();
    window.addEventListener('auth-changed', e => this.loggedIn = e.detail);
  }

  render() {
    return html`<p>${this.loggedIn ? 'Вы вошли' : 'Не вошли'}</p>`;
  }
}

customElements.define('header-component', HeaderComponent);

Безопасность и лучшие практики

  • Никогда не хранить чувствительные данные в реактивных свойствах без шифрования.
  • Использовать HTTPS для передачи токенов.
  • Проверять токен на сервере при каждом критическом запросе.
  • Разделять состояние UI и состояние аутентификации, чтобы минимизировать риски утечки данных через DOM.

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