keycloak-js: интеграция во Vue
↑ 5.5 Аутентификация и доступ: Keycloak, SSO, ACL · ← Предыдущая · → Следующая
Зачем это на собесе
Практика: инициализация 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.
Практика
- Подключите Keycloak к Vue-приложению с
check-ssoи guard-ами. - Добавьте токен в запросы и обработайте истечение refresh.
- Замокайте Keycloak в тестах.
Вопросы с ответами
check-ssoилиlogin-required?
check-ssoпроверяет существующую сессию без принудительного логина (публичные страницы возможны),login-requiredперенаправляет неавторизованных на вход.
Как обновлять токен?
keycloak.updateToken(minValidity)перед запросами и по событиюonTokenExpired.