keycloak-js: интеграция во Vue

↑ 5.5 Аутентификация и доступ: Keycloak, SSO, ACL · ← Предыдущая · → Следующая

Обязательно~4 мин чтенияУровень: middle

Зачем это на собесе

Практика: инициализация Keycloak до старта приложения, обновление токена и выход.

Объяснение

// auth/keycloak.ts
import Keycloak from "keycloak-js";
export const keycloak = new Keycloak({ url: import.meta.env.VITE_KC_URL, realm: import.meta.env.VITE_KC_REALM, clientId: import.meta.env.VITE_KC_CLIENT });
 
export async function initAuth(): Promise<boolean> {
  const authenticated = await keycloak.init({
    onLoad: "check-sso",                              // не форсировать логин; "login-required" — принудительно
    pkceMethod: "S256",
    silentCheckSsoRedirectUri: `${location.origin}/silent-check-sso.html`,
    checkLoginIframe: false,                          // iframe-проверка ломается без сторонних cookie
  });
  keycloak.onTokenExpired = () => keycloak.updateToken(30).catch(() => keycloak.login());
  keycloak.onAuthLogout = () => useAuthStore().clear();
  return authenticated;
}
// main.ts: перед монтированием приложения
await initAuth();
createApp(App).use(pinia).use(router).mount("#app");
 
// stores/auth.ts
export const useAuthStore = defineStore("auth", () => {
  const user = computed(() => keycloak.tokenParsed && { id: keycloak.tokenParsed.sub, name: keycloak.tokenParsed.preferred_username });
  const roles = computed<string[]>(() => keycloak.tokenParsed?.realm_access?.roles ?? []);
  const hasRole = (r: string) => roles.value.includes(r);
  const login = () => keycloak.login({ redirectUri: location.href });
  const logout = () => keycloak.logout({ redirectUri: location.origin });
  async function getToken() { await keycloak.updateToken(30); return keycloak.token!; }    // актуальный токен для запросов
  return { user, roles, hasRole, login, logout, getToken, isAuthenticated: computed(() => !!keycloak.authenticated) };
});
// Интерсептор Axios: токен в каждом запросе
api.interceptors.request.use(async (cfg) => { const t = await useAuthStore().getToken(); cfg.headers.Authorization = `Bearer ${t}`; return cfg; });
 
// Guard роутера
router.beforeEach((to) => { if (to.meta.requiresAuth && !keycloak.authenticated) return keycloak.login({ redirectUri: location.origin + to.fullPath }) as never; });

Реактивность: keycloak сам по себе не реактивен — оборачивайте в стор и обновляйте ref в колбэках (onAuthSuccess, onTokenExpired).

Альтернативы: oidc-client-ts (универсальнее, не привязан к Keycloak), vue-keycloak-js, @dsb-norge/vue-keycloak-js.

Тестирование: мок keycloak (vi.mock("@/auth/keycloak")), подстановка стора (createTestingPinia).

Нюансы и подводные камни

  • Инициализация должна завершиться до первого рендера и guard-ов.
  • updateToken(minValidity) обновляет только при близком истечении — вызывайте перед каждым запросом.
  • Ошибка обновления (истёк refresh) → повторный вход.
  • Бесконечные редиректы при неверных URL/redirect.
  • Токены не хранятся в Pinia persist.

Практика

  1. Подключите Keycloak к Vue-приложению с check-sso и guard-ами.
  2. Добавьте токен в запросы и обработайте истечение refresh.
  3. Замокайте Keycloak в тестах.

Вопросы с ответами

Связанные темы