v-keycloak-store
v-keycloak-store
Plugin completo de autenticação e autorização RBAC para Vue 3, integrado nativamente com Keycloak, Vue Router 4 e Pinia.
Fornece gerenciamento reativo do ciclo de vida da sessão do usuário, renovação automática de token JWT em background, verificação declarativa de papéis (Roles) e grupos (Groups) no roteador, tratamento inteligente de erro 403 (Forbidden) com auto-recuperação, diretivas de controle de exibição no DOM e tipagem estrita com TypeScript.
Sumário
- Instalação e Dependências
- Guia de Início Rápido (Quickstart)
- Guia de Uso
- Referência Completa de Funções e API
- Resolução de Problemas Comuns
1. Instalação e Dependências
Certifique-se de ter instalado as dependências fundamentais do ecossistema:
npm install v-keycloak-store keycloak-js pinia
Peer dependencies requeridas:
vue:>= 3.4.0vue-router:>= 4.3.0pinia:>= 2.1.0
2. Guia de Início Rápido (Quickstart)
Configuração básica no main.ts / main.js:
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from './router';
import App from './App.vue';
import { createKeycloak, KeycloakPlugin } from 'v-keycloak-store';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(router);
// 1. Criar a instância de conexão com o Keycloak
const keycloak = createKeycloak({
url: 'https://auth.meudominio.com',
realm: 'meu-realm',
clientId: 'meu-frontend'
});
// 2. Instalar o plugin de autenticação
app.use(KeycloakPlugin, {
keycloak,
router,
optionsKeycloak: {
onLoad: 'login-required',
checkLoginIframe: false
},
forbiddenRoute: { name: 'error-403' },
onReady: () => {
app.mount('#app');
}
});
3. Guia de Uso
3.1. Inicialização do Plugin
O KeycloakPlugin cuida de:
- Inicializar o cliente
keycloak-js. - Registrar automaticamente o guardião de rotas no Vue Router.
- Registrar as diretivas globais
v-canev-has. - Manter o ciclo de renovação automática de token ativo em segundo plano.
- Sincronizar reativamente o estado da sessão com a store do Pinia.
app.use(KeycloakPlugin, {
keycloak,
router,
optionsKeycloak: {
onLoad: 'check-sso', // ou 'login-required'
checkLoginIframe: false
},
refreshTimeout: 90000, // Intervalo em ms para checar necessidade de renovar token (padrão: 90s)
forbiddenRoute: { name: 'error-403' }, // Destino ao ter acesso negado por falta de roles
rolesMode: 'any', // Modo padrão: 'any' (ao menos uma) ou 'all' (todas as roles)
debug: false // Ativa logs detalhados de diagnóstico no console
});
3.2. Proteção de Rotas e RBAC (Vue Router)
As rotas podem ser protegidas declarativamente via meta. Todas as rotas aninhadas em to.matched são avaliadas hierarquicamente:
import { createRouter, createWebHistory } from 'vue-router';
export enum AppRoles {
ManageEquipamentos = 'INVENTARIO_MANAGE_EQUIPAMENTO',
ViewEquipamentos = 'INVENTARIO_VIEW_EQUIPAMENTO',
Admin = 'ADMIN'
}
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/publico',
component: PublicView,
// Sem meta.requiresAuth: livre acesso
},
{
path: '/dashboard',
component: DashboardView,
meta: {
requiresAuth: true // Exige apenas que o usuário esteja autenticado
}
},
{
path: '/gerenciar',
component: GerenciarLayout,
meta: {
requiresAuth: true,
roles: [AppRoles.ManageEquipamentos, AppRoles.ViewEquipamentos],
rolesMode: 'any' // Acesso concedido se possuir QUALQUER um dos papéis
},
children: [
{
path: 'criar',
component: CriarView,
meta: {
roles: [AppRoles.ManageEquipamentos] // Sobrescreve/adiciona restrição específica para a rota filha
}
}
]
},
{
path: '/403',
name: 'error-403',
component: () => import('@/views/Error403View.vue'),
meta: { title: 'Acesso Negado' }
}
]
});
3.3. Tratamento de 403 e Auto-Recuperação
Quando um usuário tenta acessar uma rota protegida por papéis que ele não possui:
- O guardião bloqueia a transição de rota.
- Redireciona o usuário para
forbiddenRouteanexando a rota de destino na query string:/403?redirect=/gerenciar/criar - Auto-Recuperação Automática: Se o usuário estiver na tela de 403 e seus papéis forem atualizados (ou trocar de perfil), o guardião resolve automaticamente o parâmetro
query.redirect. Se o usuário agora for elegível, ele é automaticamente redirecionado de volta à página de destino pretendida.
3.4. Regras Customizadas (Feature Flags, etc.)
Você pode passar regras assíncronas externas (como verificação de Feature Flags ou Tenant) através do gancho customRules:
import { createAuthGuard } from 'v-keycloak-store';
import { useFeatureFlagsStore } from '@/stores/featureFlags';
export function setupRouterGuards(router: Router) {
const guard = createAuthGuard({
router,
forbiddenRoute: { name: 'error-403' },
customRules: async (record, to) => {
if (record.meta?.featureFlag) {
const flags = useFeatureFlagsStore();
if (!flags.isLoaded) {
await flags.carregarFlags();
}
return flags.isEnabled(record.meta.featureFlag as string);
}
return true; // Libera acesso
}
});
router.beforeEach(guard);
}
3.5. Composable usePermissions
Permite inspecionar papéis e grupos de forma totalmente reativa dentro de componentes Vue:
<script setup lang="ts">
import { usePermissions } from 'v-keycloak-store';
import { AppRoles } from '@/constants/roles';
// Tipagem com enum próprio da aplicação:
const {
hasRole,
hasAnyRole,
hasAllRoles,
hasGroup,
hasAnyGroup,
hasAllGroups
} = usePermissions<AppRoles>();
function executarExclusao() {
if (!hasRole(AppRoles.ManageEquipamentos)) {
alert('Você não tem permissão para excluir!');
return;
}
// Exclusão permitida
}
</script>
<template>
<div>
<!-- Renderização condicional por papel único -->
<button v-if="hasRole(AppRoles.ManageEquipamentos)" @click="executarExclusao">
Excluir Registro
</button>
<!-- Renderização condicional por múltiplos papéis (ao menos um) -->
<section v-if="hasAnyRole([AppRoles.ManageEquipamentos, AppRoles.ViewEquipamentos])">
<TabelaEquipamentos />
</section>
<!-- Renderização condicional por grupo -->
<aside v-if="hasGroup('ti')">
Painel Técnico
</aside>
</div>
</template>
3.6. Diretivas Customizadas v-can e v-has
As diretivas v-can e v-has (sinônimos) removem o elemento fisicamente do DOM caso o usuário não atenda às condições:
<!-- Verificando Papel Único -->
<button v-can:role="'ADMIN'">Ação Administrativa</button>
<!-- Verificando Lista de Papéis (ao menos um) -->
<div v-can:role="['ADMIN', 'GESTOR']">Painel de Controle</div>
<!-- Verificando Grupo Único -->
<span v-can:group="'ti'">Apenas Membros do TI</span>
<!-- Verificando Lista de Grupos (ao menos um) -->
<nav v-can:group="['ti', 'financeiro']">Links Internos</nav>
3.7. Gerenciamento de Estado Global (useKeycloakStore)
Acesso direto às propriedades e ações de autenticação:
<script setup lang="ts">
import { useKeycloakStore } from 'v-keycloak-store';
const authStore = useKeycloakStore();
console.log(authStore.token); // Token JWT em string
console.log(authStore.token_decode); // Payload decodificado do JWT
console.log(authStore.isAuthenticated);// true se houver token válido
console.log(authStore.username); // preferred_username
console.log(authStore.name); // Nome completo do usuário
console.log(authStore.email); // E-mail cadastrado
console.log(authStore.gravatar); // URL de avatar do Gravatar gerado pelo e-mail
console.log(authStore.roles); // Array de strings com os papéis do usuário
console.log(authStore.groups); // Array de strings com os grupos do usuário
function deslogar() {
authStore.logoutAction('Logout solicitado pelo usuário');
}
</script>
4. Referência Completa de Funções e API
createKeycloak(options)
Fábrica responsável por instanciar o adaptador Keycloak oficial.
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
options.url |
string |
Sim | URL base do servidor Keycloak. |
options.realm |
string |
Sim | Nome do realm configurado. |
options.clientId |
string |
Sim | Identificador do cliente OpenID Connect. |
Retorno: Instância tipada de Keycloak.
KeycloakPlugin
Plugin do Vue 3 instalado com app.use(KeycloakPlugin, options).
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
keycloak |
Keycloak |
Obrigatório | Instância retornada por createKeycloak(). |
router |
Router |
Obrigatório | Instância do roteador Vue Router. |
optionsKeycloak |
KeycloakInitOptions |
{} |
Opções repassadas ao keycloak.init() nativo. |
forbiddenRoute |
RouteLocationRaw |
undefined |
Rota para redirecionar em caso de 403 (ex: { name: 'error-403' }). |
rolesMode |
'any' | 'all' |
'any' |
Modo padrão de validação de papéis em rotas. |
groupsMode |
'any' | 'all' |
'any' |
Modo padrão de validação de grupos em rotas. |
refreshTimeout |
number |
90000 |
Frequência em milissegundos para verificação e renovação de token. |
deactivateTimeout |
boolean |
false |
Se true, desativa o temporizador de renovação periódica. |
customRules |
(record, to) => boolean | Promise<boolean> |
undefined |
Hook assíncrono para validações personalizadas. |
onForbidden |
(to) => void |
undefined |
Callback executado ao ocorrer acesso proibido por falta de permissão. |
onReady |
() => void |
undefined |
Callback disparado após inicialização bem-sucedida do Keycloak. |
onLogin |
() => void |
undefined |
Callback disparado após login bem-sucedido. |
onLogout |
() => void |
undefined |
Callback disparado após término de sessão. |
onError |
(err) => void |
undefined |
Callback disparado em caso de erro na inicialização. |
debug |
boolean |
false |
Ativa logs detalhados da biblioteca no console. |
usePermissions<TRole>()
Composable reativo para checagem de papéis e grupos.
function usePermissions<TRole extends string = string>(customStore?: KeycloakStore): UsePermissionsReturn<TRole>
Métodos retornados:
hasRole(role: TRole | string): boolean: Retornatruese o usuário possuir o papel indicado. Suporta normalização de caixa alta e caminhos de realm.hasAnyRole(roles: (TRole | string)[]): boolean: Retornatruese o usuário possuir ao menos um dos papéis informados. Retornatruese a lista for vazia.hasAllRoles(roles: (TRole | string)[]): boolean: Retornatrueapenas se o usuário possuir todos os papéis informados. Retornatruese a lista for vazia.hasGroup(group: string): boolean: Retornatruese o usuário pertencer ao grupo indicado.hasAnyGroup(groups: string[]): boolean: Retornatruese pertencer a ao menos um dos grupos informados.hasAllGroups(groups: string[]): boolean: Retornatruese pertencer a todos os grupos informados.
createAuthGuard(options)
Gera uma função de guardião de navegação (NavigationGuard) para acoplamento manual com router.beforeEach(guard).
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
router |
Router |
Opcional | Instância do roteador para resolver redirects de 403. |
forbiddenRoute |
RouteLocationRaw |
Opcional | Rota de destino em caso de falta de permissão. |
keycloak |
Keycloak |
Opcional | Resolução automática a partir da store se omitido. |
store |
KeycloakStore |
Opcional | Resolução automática de useKeycloakStore() se omitido. |
rolesMode |
'any' | 'all' |
'any' |
Modo de avaliação dos papéis. |
groupsMode |
'any' | 'all' |
'any' |
Modo de avaliação dos grupos. |
customRules |
(record, to) => ... |
Opcional | Hook customizado para regras adicionais (Feature Flags). |
onForbidden |
(to) => void |
Opcional | Callback acionado ao negar acesso por papéis/grupos. |
useKeycloakStore()
Store Pinia com o estado global da sessão.
Estado e Getters:
token: String com o token de acesso JWT atual.token_decode: Objeto com o payload decodificado do token.isAuthenticated: Booleano reativo indicando se o usuário possui sessão ativa.id:subdo usuário no Keycloak.username: Nome de usuário (preferred_username).name: Nome completo.email: E-mail do usuário.roles: Lista de papéis (roles) extraídos do token.groups: Lista de grupos (groups) extraídos do token.gravatar: URL do avatar gerado via Gravatar.extend: Objeto para armazenar propriedades dinâmicas adicionais.
Ações principais:
getDataKeycloak(): Recarrega e sincroniza os dados do token com a store.removeDataKeycloak(): Limpa o estado da store.logoutAction(motivo?: string): Executa logout seguro no Keycloak e registra histórico de diagnóstico.hasRole(role: string): Avalia papel diretamente pela store.hasAnyRole(roles: string[]): Avalia papéis no modo any.hasAllRoles(roles: string[]): Avalia papéis no modo all.hasGroup(group: string): Avalia grupo diretamente pela store.hasAnyGroup(groups: string[]): Avalia grupos no modo any.hasAllGroups(groups: string[]): Avalia grupos no modo all.setExtend(key: string, value: unknown): Adiciona chave ao objeto estendido.removeExtend(key: string): Remove chave do objeto estendido.setModoDebug(valor: boolean): Habilita ou desabilita logs internos.registrarLogDeslogamento(motivo: string, fatal?: boolean): Salva evento no histórico local (historico_deslogamento).
Extensão de Tipos RouteMeta
A biblioteca amplia automaticamente o módulo vue-router com tipos estritos:
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean;
roles?: string[] | readonly string[];
rolesMode?: 'any' | 'all';
groups?: string[] | readonly string[];
groupsMode?: 'any' | 'all';
[key: string]: unknown;
}
}
5. Resolução de Problemas Comuns
1. Uncaught SyntaxError: ... doesn't provide an export named: 'usePermissions'
O Vite armazena dependências de terceiros pré-empacotadas em cache em node_modules/.vite. Ao atualizar a biblioteca localmente, execute o servidor de desenvolvimento com limpeza de cache:
npm run dev -- --force
2. Conflito de tsconfigRootDir no ESLint
Se o seu projeto emitir Parsing error: No tsconfigRootDir was set, and multiple candidate TSConfigRootDirs are present, configure explicitamente no seu eslint.config.ts:
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
},
}
Related Packages
A simple authentication and authorization library for Vue.js using typescript
A configurable Vue 3 plugin that adds router guards, axios interceptors, and pin...
Vue 3 components, a Pinia session store and permission checks for apps that sign...
The Authup admin console: a Vue single-page app for managing realms, users, clie...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 1.5.1 | >=3.4.0 | ISC | |
| 1.5.0 | >=3.4.0 | ISC | |
| 1.4.3 | >=3.4.0 | ISC | |
| 1.4.2 | >=3.4.0 | ISC | |
| 1.4.1 | >=3.4.0 | ISC | |
| 1.4.0 | ^3.4.0 | ISC | |
| 1.3.0 | ^3.4.0 | ISC | |
| 1.2.6 | ^3.4.0 | ISC | |
| 1.2.5 | ^3.4.0 | ISC | |
| 1.2.4 | ^3.4.0 | ISC | |
| 1.2.3 | ^3.4.0 | ISC | |
| 1.2.2 | ^3.4.0 | ISC | |
| 1.2.1 | ^3.4.0 | ISC | |
| 1.2.0 | ^3.4.0 | ISC | |
| 1.1.3 | ^3.4.0 | ISC | |
| 1.1.2 | ^3.4.0 | ISC | |
| 1.1.1 | ^3.4.0 | ISC | |
| 1.1.0 | ^3.4.0 | ISC | |
| 1.0.1 | ^3.4.0 | ISC | |
| 1.0.0 | ^3.4.0 | ISC |