Phase 1a из плана: только swap библиотеки, realm/URL остаются на
auth.nstart.space/realms/nstart. Phase 2 (intgr с altum auth-service)
— отдельный эпик.
Зачем. oidc-client-ts с automaticSilentRenew триггерил бесконечный
POST /token loop когда refresh_token истекал (см. MR !269 revert,
~30+ 400'к в console, страница залипала на 403). Altum давно ушёл
с oidc-client-ts на keycloak-js по той же причине.
Новая модель.
- src/lib/keycloak.ts — singleton + initKeycloak(check-sso, PKCE) +
ensureFreshToken(N) wrapper над kc.updateToken(). Зеркало
altum/src/lib/keycloak.ts.
- src/stores/authStore.ts — Zustand store: {user, isAuthenticated,
isLoading, error}. checkAuth() ждёт initKeycloak, wireKeycloakEvents()
→ onAuthSuccess/Refresh/Logout/TokenExpired re-снэпшотят store.
- src/auth/useAuth.ts — compat shim для react-oidc-context API
(auth.user.profile, isAuthenticated, signinSilent, signinRedirect,
signoutRedirect, removeUser, error). Все 11 callsites продолжают
работать без правок (только сменили путь import'а).
- src/api/client.ts — request interceptor дёргает ensureFreshToken(30)
ПЕРЕД каждым запросом + attaches Bearer ${getToken()}. 401 interceptor
делает ensureFreshToken(0) + retry один раз. Никаких таймеров, никаких
setAccessToken/setUnauthorizedHandler holder'ов — keycloak-js сам source
of truth.
- src/main.tsx — убран <AuthProvider>, <TokenSync />. Просто
useAuthStore.getState().checkAuth() на старте, потом обычный render.
- public/silent-check-sso.html — endpoint для silentCheckSsoRedirectUri.
Удалены.
- src/auth/TokenSync.tsx — не нужен, ensureFreshToken на запросах.
- src/auth/oidcConfig.ts — не нужен, конфиг внутри lib/keycloak.ts.
- npm deps: react-oidc-context, oidc-client-ts.
Добавлены deps: keycloak-js@26.2.4, zustand@5.0.13.
Tests: 171/171 passed, TSC чистый.
Migration callsites — все unchanged по семантике благодаря compat shim.
react-oidc-context + oidc-client-ts. Confidential client тоже работает —
SPA просто не использует client_secret.
- src/auth/oidcConfig.ts — UserManager settings (authority, redirect_uri,
PKCE, automaticSilentRenew). VITE_KEYCLOAK_{URL,REALM,CLIENT_ID} с дефолтами
на nstart realm + ordinis client.
- src/auth/TokenSync.tsx — синкает access_token в localStorage и axios default
header после login/refresh. Existing API код не трогали — interceptor уже
читает localStorage.
Bonus: 401 interceptor триггерит signinSilent → signinRedirect fallback.
- src/auth/AuthBadge.tsx — header'ный бейдж: "Войти" если anonymous, имя +
logout если залогинен.
- main.tsx обёрнут в <AuthProvider>.
- __root.tsx показывает AuthBadge рядом с language switch.
- .env.example с конфигом Keycloak.
- i18n auth.login / auth.logout (RU + EN).
Keycloak client `ordinis` — настроить:
Valid redirect URIs: https://ordinis.k8s.265.nstart.cloud/*,
https://ordinis.k8s.264.nstart.cloud/*,
http://localhost:5173/*
Web Origins: те же + (или "+"), PKCE Code Challenge: S256