Защита маршрутов и авторизация

Mithril — это легковесный JavaScript-фреймворк для создания одностраничных приложений (SPA), в котором маршрутизация и управление состоянием играют ключевую роль. Защита маршрутов и авторизация пользователей строится на логике проверки состояния приложения и динамического управления доступом к компонентам и страницам.

В Mithril маршруты определяются с помощью m.route(), где каждому пути соответствует компонент. Простейшая маршрутизация выглядит так:

m.route(document.body, "/", {
    "/": Home,
    "/dashboard": Dashboard,
    "/login": Login
});

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

const Auth = {
    user: null,
    login(userData) {
        this.user = userData;
    },
    logout() {
        this.user = null;
    },
    isAuthenticated() {
        return this.user !== null;
    }
};

Перехват переходов и редирект

Mithril позволяет использовать функцию onmatch при определении маршрута. Она выполняется до того, как компонент будет загружен, что делает её идеальной для проверки авторизации:

m.route(document.body, "/", {
    "/": Home,
    "/dashboard": {
        onmatch() {
            if (!Auth.isAuthenticated()) {
                return Login;
            }
            return Dashboard;
        }
    },
    "/login": Login
});

В этом примере, если пользователь не авторизован, попытка перейти на /dashboard приведёт к загрузке компонента Login. Этот подход позволяет динамически контролировать доступ к страницам.

Защита маршрутов с асинхронной проверкой

В реальных приложениях часто необходимо проверять авторизацию на сервере, используя API. Mithril поддерживает асинхронные операции в onmatch, возвращая промис:

m.route(document.body, "/", {
    "/profile": {
        onmatch() {
            return m.request({ method: "GET", url: "/api/check-auth" })
                .then(response => {
                    if (response.authenticated) {
                        return Profile;
                    } else {
                        return Login;
                    }
                });
        }
    }
});

Такая конструкция позволяет выполнять асинхронную проверку прав доступа без нарушения логики маршрутизации.

Динамическое отображение контента в компонентах

Помимо защиты маршрутов, авторизацию часто реализуют внутри компонентов, управляя видимостью элементов интерфейса:

const Navbar = {
    view() {
        return m("nav", [
            m("a[href='/']", "Главная"),
            Auth.isAuthenticated() ? m("a[href='/dashboard']", "Панель") : null,
            Auth.isAuthenticated() ? m("button", { onclick: () => Auth.logout() }, "Выйти") :
                                     m("a[href='/login']", "Войти")
        ]);
    }
};

Таким образом, элементы интерфейса подстраиваются под состояние пользователя, создавая ощущение защищённого пространства.

Middleware-подход через обёртку компонентов

Для централизованного контроля доступа удобно использовать обёртку компонентов. Создаётся функция, которая принимает компонент и возвращает новый с проверкой авторизации:

function Protected(component) {
    return {
        oninit(vnode) {
            if (!Auth.isAuthenticated()) {
                m.route.set("/login");
            }
        },
        view(vnode) {
            return Auth.isAuthenticated() ? m(component, vnode.attrs) : null;
        }
    };
}

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

m.route(document.body, "/", {
    "/dashboard": Protected(Dashboard),
    "/login": Login
});

Такой подход обеспечивает единый механизм защиты всех приватных компонентов, упрощает поддержку и масштабирование приложения.

Управление ролями и правами

В сложных приложениях требуется различать уровни доступа. Можно расширить объект Auth и проверку маршрутов:

const Auth = {
    user: null,
    login(userData) { this.user = userData; },
    logout() { this.user = null; },
    hasRole(role) { return this.user && this.user.roles.includes(role); }
};

m.route(document.body, "/", {
    "/admin": {
        onmatch() {
            if (!Auth.isAuthenticated() || !Auth.hasRole("admin")) {
                return Login;
            }
            return AdminPanel;
        }
    }
});

Так реализуется контроль доступа по ролям, позволяющий разделять права пользователей и администраторов.

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

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

const Auth = {
    user: JSON.parse(localStorage.getItem("user")),
    login(userData) {
        this.user = userData;
        localStorage.setItem("user", JSON.stringify(userData));
    },
    logout() {
        this.user = null;
        localStorage.removeItem("user");
    },
    isAuthenticated() {
        return this.user !== null;
    }
};

Это позволяет поддерживать сессию даже при обновлении браузера и интегрировать защиту маршрутов с хранением состояния.

Практические рекомендации

  • Использовать onmatch для защиты маршрутов, чтобы минимизировать рендеринг нежелательных компонентов.
  • Центрально хранить состояние авторизации в отдельном сервисе.
  • Реализовывать асинхронную проверку при необходимости обращения к API.
  • Применять обёртки компонентов (Protected) для унификации логики защиты.
  • Разделять права доступа через роли и права пользователей.
  • Сохранять состояние авторизации между сессиями с помощью localStorage или sessionStorage.

За счёт этих подходов Mithril позволяет создавать безопасные SPA с гибким контролем доступа, при этом сохраняя лёгкость и быстроту работы фреймворка.