Перейти к основному содержимому

@mineflow/auth-web

Web-адаптер аутентификации MineFlow Client SDK. Реализует порт TokenProvider из @mineflow/client-core поверх keycloak-js: PKCE-флоу, упреждающий авто-refresh access-token'а и маппинг Keycloak realm-ролей в канонический SystemRole.

Что это и зачем

По ADR-0042 SDK разбит на слои. @mineflow/auth-web — это L3, платформенный auth-адаптер для браузера. Его задача узкая: дать остальным слоям SDK два метода — «откуда взять свежий access-token» (getToken) и «какие у пользователя роли» (getRoles). Транспорт, хуки и схемы живут в других пакетах; здесь только мост между keycloak-js и абстрактным портом TokenProvider.

Адаптер web-only: keycloak-js завязан на браузерное окружение (redirect-флоу, silent-SSO через iframe). Для React Native существует отдельный пакет-аналог @mineflow/auth-native на react-native-app-auth, реализующий тот же интерфейс TokenProvider. Ниже подробнее в разделе React Native.

Где он в стеке

В 95% веб-кейсов вы используете @mineflow/auth-web вместе с @mineflow/client-react (хуки + MineflowProvider) и @mineflow/api-zod (валидация форм). См. обзор слоёв в client-core.

Установка

pnpm add @mineflow/auth-web
pnpm add keycloak-js # peer-зависимость, ^26.0.0

keycloak-js объявлен как peerDependency (^26.0.0) — вы устанавливаете его сами и инициализируете один раз на старте приложения. Сам пакет зависит только от @mineflow/client-core (для типа порта TokenProvider).

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

Шаг 1 — инициализируйте keycloak-js (один раз при старте):

import Keycloak from 'keycloak-js';
import { KeycloakTokenProvider } from '@mineflow/auth-web';

const keycloak = new Keycloak({
url: import.meta.env.VITE_KEYCLOAK_URL, // например http://localhost:8080
realm: 'mineflow',
clientId: 'mineflow-web',
});

await keycloak.init({
onLoad: 'check-sso',
pkceMethod: 'S256',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
});

// Оборачиваем в TokenProvider
const tokenProvider = new KeycloakTokenProvider({ keycloak });

Шаг 2 — отдайте провайдер и роли в MineflowProvider из @mineflow/client-react:

import { MineflowProvider } from '@mineflow/client-react';

<MineflowProvider
baseUrl={import.meta.env.VITE_API_BASE}
tokenProvider={tokenProvider}
generateId={() => crypto.randomUUID()}
roles={tokenProvider.getRoles()}
>
{children}
</MineflowProvider>;

После этого getToken вызывается транспортом SDK автоматически перед каждым запросом — заголовок Authorization: Bearer <token> подставляется без вашего участия, а токен обновляется по порогу minValiditySeconds.

Логин и логаут

KeycloakTokenProvider намеренно не оборачивает вход/выход — управляйте ими через keycloak-js напрямую:

keycloak.login(); // redirect на страницу входа Keycloak
keycloak.logout(); // redirect на logout-эндпоинт
keycloak.authenticated; // boolean — текущее состояние

Типичный паттерн: при ответе API 401 (после того как getToken({ forceRefresh: true }) не помог) UI инициирует keycloak.login().

Ключевые экспорты

Пакет реэкспортирует ровно следующее (см. полный перечень в API-референсе):

ЭкспортТипНазначение
KeycloakTokenProviderclassРеализация порта TokenProvider поверх keycloak-js.
KeycloakTokenProviderOptionsinterfaceОпции конструктора: keycloak, minValiditySeconds?.
mapKeycloakRolesfunctionЧистая функция: Keycloak realm-роли → SystemRole[].
KEYCLOAK_ROLE_ALIASESconstКарта lowercase-алиасов Keycloak в канонические SystemRole.
Реэкспорт ядра

mapKeycloakRoles и KEYCLOAK_ROLE_ALIASES физически живут в @mineflow/client-core (их разделяют web- и RN-адаптеры) и реэкспортируются отсюда для совместимости публичного API auth-web.

new KeycloakTokenProvider(opts)

interface KeycloakTokenProviderOptions {
keycloak: Keycloak; // инициализированный экземпляр keycloak-js
minValiditySeconds?: number; // порог авто-refresh, по умолчанию 30
}

Методы реализуют порт TokenProvider:

  • getToken(opts?: GetTokenOptions): Promise<string | null> — вызывает keycloak.updateToken(minValidity) и отдаёт keycloak.token. Если в опциях forceRefresh: true, форсит обновление через updateToken(-1) независимо от остаточной валидности (используется реактивно транспортом при 401). Если refresh не удался — возвращает текущий токен (или null): пусть API ответит 401, а UI запустит повторный login. Тип GetTokenOptions определён в client-core.

  • getRoles(): readonly SystemRole[] — берёт keycloak.realmAccess?.roles и пропускает их через mapKeycloakRoles, возвращая канонические роли в PascalCase.

mapKeycloakRoles(realmRoles): SystemRole[]

Чистая функция: переводит lowercase Keycloak-алиасы в канонический SystemRole, дедуплицирует результат и отбрасывает неизвестные/инфраструктурные роли (offline_access, uma_authorization и т.п.).

import { mapKeycloakRoles } from '@mineflow/auth-web';

mapKeycloakRoles(['foreman', 'engineer', 'offline_access']);
// → ['Foreman', 'Engineer']

KEYCLOAK_ROLE_ALIASES

Карта соответствия алиасов:

Keycloak (lowercase)SystemRole (canonical)
ceoCEO
engineerEngineer
foremanForeman
mechanicMechanic
adminAdmin
otibOtibSpecialist
supplySupply

Подводные камни и важные правила

keycloak-js — только браузер

Этот пакет нельзя использовать в React Native: keycloak-js опирается на DOM, iframe для silent-SSO и redirect-навигацию. В RN берите @mineflow/auth-native.

Инициализируйте keycloak до создания провайдера

KeycloakTokenProvider ожидает уже инициализированный экземпляр (await keycloak.init(...)). Вызывать init нужно один раз на всё приложение; повторная инициализация ломает silent-SSO.

Поведение при провале refresh — by design

getToken никогда не бросает исключение при неудачном refresh: он возвращает текущий (возможно протухший) токен. Это сознательная мягкая деградация — следующий запрос получит 401, транспорт сделает один retry с forceRefresh: true, и если он тоже не пройдёт, UI должен перенаправить на login. Не оборачивайте getToken в собственный try/catch с редиректом — это уже учтено в цепочке.

Дрейф маппинга ролей

KEYCLOAK_ROLE_ALIASES — единственная допущенная в SDK копия серверного списка ролей (серверный barrel libs/auth тянет NestJS и непригоден на фронте). Карта покрыта сторож-тестом на 7 канонических ролей; при добавлении 8-й роли на бэке маппинг и тест нужно обновить (ADR-0042 follow-up).

React Native

@mineflow/auth-web в RN не работает. Используйте @mineflow/auth-native, который реализует тот же порт TokenProvider ({ getToken, getRoles? }) поверх react-native-app-auth (нативный OIDC через системный браузер). Остальной код SDK при этом не меняется — MineflowProvider принимает любой TokenProvider, а различается только адаптер. Подробности интеграции — в рецепте React Native.

Ссылки