@mineflow/auth-native
@mineflow/auth-native — это L3-адаптер аутентификации client-SDK для React Native. Он содержит один класс — ReactNativeTokenProvider, который реализует порт TokenProvider из @mineflow/client-core поверх результата нативного OIDC-флоу (типично — react-native-app-auth, но не привязан к нему).
По слоистой архитектуре ADR-0042 всё платформо-специфичное инъектируется адаптерами, чтобы ядро (fetch, идемпотентность, ошибки, саги, пагинация, SSE) писалось один раз и работало в web и RN. На web эту роль играет @mineflow/auth-web поверх keycloak-js; в RN использовать keycloak-js нельзя (browser-only — window, redirect-флоу), поэтому логин идёт через нативный OIDC, а auth-native превращает его токены в порт, который понимает ядро: «дай актуальный access-token и канонические роли».
Пакет не зависит от react-native-app-auth напрямую. Приложение само владеет auth-состоянием и прокидывает в адаптер две функции — getState (вернуть текущее состояние) и refresh (дёрнуть нативный refresh). За счёт этого адаптер тестируем, бандл-безопасен и не привязан к конкретной OIDC-библиотеке RN — подойдёт и expo-auth-session.
Установка
pnpm add @mineflow/auth-native
# нативный OIDC-флоу в самом RN-приложении (peer, не зависимость SDK):
pnpm add react-native-app-auth
Единственная runtime-зависимость пакета — @mineflow/client-core (workspace:*), откуда берутся порт TokenProvider, тип SystemRole и хелперы декодирования JWT. В монорепо MineFlow пакеты резолвятся как workspace:*.
react-native-app-auth указан как пример: SDK его не импортирует. Подойдёт любая библиотека, дающая accessToken / refreshToken / дату истечения. Полный RN-стек (помимо auth) требует ещё пары адаптеров — см. React Native ниже.
Использование
Логином и хранением auth-состояния владеет приложение. Адаптер только читает это состояние и при необходимости упреждающе рефреши т токен.
import { authorize, refresh } from 'react-native-app-auth';
import { ReactNativeTokenProvider } from '@mineflow/auth-native';
const config = {
issuer: 'https://auth.mineflow.local/realms/mineflow',
clientId: 'mineflow-mobile',
redirectUrl: 'com.mineflow://oauthredirect',
scopes: ['openid', 'profile', 'roles'],
};
// 1. Логин в нативном браузере; состояние хранит приложение
let authState = await authorize(config);
// authState: { accessToken, refreshToken, accessTokenExpirationDate, ... }
// 2. Адаптер отдаёт ядру актуальный токен (упреждающий refresh) + роли
const tokenProvider = new ReactNativeTokenProvider({
getState: () => authState,
refresh: async () => {
authState = await refresh(config, { refreshToken: authState.refreshToken! });
return authState;
},
});
Дальше провайдер передаётся в MineflowProvider из @mineflow/client-react. Роли резолвятся из access-token синхронно через getRoles():
import 'react-native-get-random-values'; // polyfill для crypto.randomUUID
import { MineflowProvider } from '@mineflow/client-react';
<MineflowProvider
baseUrl={API_BASE} // origin API; пути из api-client уже содержат /api/v1
tokenProvider={tokenProvider}
generateId={() => crypto.randomUUID()}
roles={tokenProvider.getRoles()}
>
{children}
</MineflowProvider>;
После этого все хуки client-react (useAssets, useApproveShiftReport, …) и RBAC-гейты работают как на web — UI пишется на RN-примитивах, бизнес-логика переиспользуется как есть.
Ключевые экспорты
Пакет реэкспортирует ровно три символа из react-native-token-provider.
| Экспорт | Тип | Назначение |
|---|---|---|
ReactNativeTokenProvider | class implements TokenProvider | адаптер: упреждающий/реактивный refresh + маппинг ролей |
ReactNativeTokenProviderOptions | interface | опции конструктора (getState, refresh, …) |
NativeAuthState | interface | подмножество результата нативного OIDC, которое читает адаптер |
ReactNativeTokenProviderOptions
| Опция | Тип | По умолчанию | Назначение |
|---|---|---|---|
getState | () => NativeAuthState | null | — | вернуть текущее auth-состояние (хранит приложение) |
refresh | () => Promise<NativeAuthState> | — | дёрнуть нативный refresh, сохранить и вернуть новое состояние |
minValiditySeconds? | number | 30 | запас до истечения для упреждающего refresh |
now? | () => number | Date.now | источник «сейчас» в мс (для тестов) |
NativeAuthState
Подмножество AuthorizeResult / RefreshResult из react-native-app-auth — адаптеру нужны только эти поля:
interface NativeAuthState {
accessToken: string;
refreshToken?: string;
/** ISO datetime истечения access-token (поле accessTokenExpirationDate). */
accessTokenExpirationDate?: string;
}
Методы порта TokenProvider
getToken(opts?)— еслиopts.forceRefresh(выставляется ядром реактивно при ответе 401) или access-token истекает в ближайшиеminValiditySeconds, вызываетrefresh()и отдаёт свежийaccessToken; иначе возвращает текущий. При ошибкеrefresh()возвращается текущий токен (API ответит 401 → UI инициирует повторный login). ЕслиgetState()вернулnull—getToken()отдаётnull.getRoles()— декодирует realm-роли из access-token (черезrealmRolesFromTokenиз client-core) и маппит их в каноническиеSystemRole(mapKeycloakRoles). При отсутствии состояния возвращает[].
forceRefresh адаптером поддержан полностью: ядро (makeAuthenticatedFetch из client-core) при 401 повторяет запрос с { forceRefresh: true }. Это покрывает случаи, когда упреждающий refresh не сработал или токен протух «в полёте» — например отложенная offline-мутация.
Подводные камни и важные правила
Адаптер не хранит токены и не вызывает authorize. Внутри refresh обязательно сохрани новое состояние туда же, откуда читает getState — иначе следующий getToken() снова получит протухший токен. В примере выше это общая переменная authState; в реальном приложении — стейт-стор / secure storage.
Окно «истекает скоро» считается из accessTokenExpirationDate. Если поле не передано (или не парсится как дата), isExpiringSoon всегда false — упреждающего refresh не будет, останется только реактивный по 401. react-native-app-auth это поле отдаёт; при другой OIDC-библиотеке убедись, что мапишь его в NativeAuthState.
Если refresh() бросил, getToken() молча отдаёт текущий (возможно протухший) токен — запрос уйдёт, бэк ответит 401, и UI начнёт login заново. Не нужно ловить ошибки refresh внутри адаптера — это сделано by design.
keycloak-js — browser-only. Для RN канон — нативный OIDC + ReactNativeTokenProvider. Не пытайся тянуть @mineflow/auth-web в React Native.
React Native
auth-native закрывает только аутентификацию. Полный RN-стек требует ещё двух платформенных адаптеров, инъектируемых через тот же MineflowProvider:
generateId— нативногоcrypto.randomUUIDнет: подключиreact-native-get-random-values(импорт до использования) и передайgenerateId={() => crypto.randomUUID()}. Нужен для авто-Idempotency-Keyна всех write.- SSE-fetch — дефолтный RN-fetch не отдаёт потоковый
ReadableStream, поэтомуuseNotificationFeedбез потокового fetch не заработает. Передай в client-core свойfetchImplс потоковой поддержкой (react-native-fetch-api).
Подробности по сквозному RN-стеку — в рецепте React Native. По RBAC-гейтам поверх getRoles() — рецепт RBAC.
Адаптер написан и покрыт юнит-тестами (логика refresh, маппинг ролей), но в живом RN-рантайме ещё не прогонялся (нет RN-приложения на этом адаптере). По бандлу browser/RN-safe.
Ссылки
- Полный API-референс из кода → /api/auth-native/
- REST API (Redoc) → /rest/
- Web-аналог адаптера →
@mineflow/auth-web - Порты (
TokenProvider,GetTokenOptions), хелперы JWT/ролей →@mineflow/client-core - Провайдер и хуки →
@mineflow/client-react - Рецепты → React Native, RBAC, SSE, offline