pmvc-vue-permissions
pmvc-vue-permissions
Integração de permissões para Vue 3 com Quasar CLI ou Vite, usando o adapter oficial do Keycloak. O pacote fornece diretiva, composable, bootstrap, store gerada, guard de rotas, interceptadores Axios e uma CLI de migração.
- npm: https://www.npmjs.com/package/pmvc-vue-permissions
- backend NestJS: https://www.npmjs.com/package/pmvc-nest-permissions
Modelo de autorização
As permissões do sistema são client roles do ServiHub. A biblioteca lê
somente as roles do cliente informado em KEYCLOAK_CLIENT_ID:
token.resource_access[KEYCLOAK_CLIENT_ID].roles
Elas ficam disponíveis em keycloakAuthStore.permissions e podem ter nomes
como servihub-usuarios-visualizar ou servihub-contratos-editar. As realm
roles são mantidas separadamente em realmRoles e só são consultadas quando a
aplicação pede isso de forma explícita.
Essa separação impede que uma realm role genérica, como admin, conceda por
acidente uma permissão específica do ServiHub.
Requisitos e instalação
- Node.js 18 ou superior;
- Vue 3;
- Quasar 2, via Quasar CLI ou
@quasar/vite-plugin.
npm install pmvc-vue-permissions
keycloak-js é uma dependência direta do pacote e é instalado junto. O projeto
não precisa criar outra instância do adapter.
Durante o init, a geração usa uma única barra verde inspirada no progresso do
Docker. Ela acumula os pontos da esquerda para a direita e não reinicia o
percurso; ao concluir, preenche os pontos restantes.
Inicialização no Quasar CLI ou Vue/Vite
Na raiz do frontend:
npx pmvc-permissions init
O assistente mostra uma pergunta por vez. Em um terminal interativo, a sugestão
já aparece preenchida na linha de entrada: use Backspace para substituí-la ou
apenas Enter para aceitá-la. Uma entrada inválida repete a mesma pergunta.
Valores existentes no .env são usados como sugestões. Uma trilha à esquerda
usa ◆ para a pergunta atual, │ para conectar as etapas e ● ✓ para manter
visível o histórico concluído.
Para revisar sem gravar:
npx pmvc-permissions init --dry-run
Para automação:
npx pmvc-permissions init \
--client servihub \
--realm PMVC \
--url https://keycloak.exemplo.gov.br \
--no-interactive
Opções:
| Opção | Efeito |
|---|---|
--dir <caminho> |
Seleciona o projeto Quasar; o padrão é o diretório atual. |
--url <url> |
Define a URL do Keycloak. |
--realm <nome> |
Define o realm; o padrão é PMVC. |
--client <id> |
Define o client ID público usado para ler as client roles. |
--dry-run |
Mostra o plano sem escrever arquivos. |
--interactive |
Força o questionário, inclusive com entrada redirecionada. |
--no-interactive |
Usa flags, .env e padrões sem perguntar. |
Arquivos criados
O init gera:
src/plugins/keycloak.js: proxy compatível com imports antigos;src/stores/keycloakAuthStore.js: estado exclusivo da autenticação Keycloak;src/boot/keycloak.js: inicialização centralizada comKeycloakBoot;src/boot/keycloak-router.js: guard declarativo de rotas;src/boot/axios.js: bearer token, renovação e tratamento de 401/403;src/pages/UnauthorizedPage.vue: destino das recusas de acesso;public/silent-check-sso.html: retorno do SSO silencioso;.env.keycloak.example: variáveis públicas do frontend;docs/keycloak-migration/permissions.todo.md: ocorrências antigas que ainda precisam ser convertidas em client roles específicas;AGENTS.md: ponto de entrada para agentes que continuarem a migração;AGENTS/context/keycloak-pmvc-vue.md: arquitetura e responsabilidades da integração Vue/Quasar, inclusive a fronteira entre a UI de grupos e o Admin Client exclusivo do backend;AGENTS/skills/finish-pmvc-keycloak-vue/SKILL.md: roteiro pós-init para remover o auth legado e validar SSO, rotas, API e navegação lazy de grupos.
Os arquivos de agentes são preservados quando já existem. Assim, instruções do projeto não são sobrescritas; o contexto e o skill específicos da biblioteca continuam sendo gerados quando seus próprios caminhos estão livres. As instruções exigem respeito à arquitetura encontrada e uma postura grill-me: questionar premissas de domínio sem interromper decisões técnicas sustentadas por evidências do próprio projeto.
Em projetos Quasar CLI, o quasar.config.js recebe os boots na ordem
keycloak, keycloak-router e depois os demais, além das variáveis de ambiente
ausentes. A rota /unauthorized é adicionada antes do catch-all em
src/router/routes.js.
Em projetos Vue/Vite com @quasar/vite-plugin, a CLI reconhece
vite.config.ts, vite.config.js, vite.config.mts ou vite.config.mjs e
gera src/plugins/pmvc-keycloak.ts e
src/stores/keycloakAuthStore.ts. O frontend usa as variáveis públicas:
VITE_KEYCLOAK_URL=https://keycloak.exemplo.gov.br
VITE_KEYCLOAK_REALM=PMVC
VITE_KEYCLOAK_CLIENT_ID=gantt-client
Quando src/main.ts possui um bootstrap simples com createApp() e
createPinia(), o init registra o Keycloak antes de app.mount(). Se detectar
login JWT/local existente, preserva o bootstrap e registra a migração como
pendência para não ativar dois mecanismos concorrentes.
O boot usa:
{
onLoad: 'check-sso',
pkceMethod: 'S256',
checkLoginIframe: false,
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
}
Assim, páginas públicas continuam públicas. Cada rota decide se exige apenas login ou também uma client role.
Regras de rota
const routes = [
// Pública: não abre o login automaticamente.
{ path: '/', component: () => import('pages/HomePage.vue') },
// Exige autenticação, sem exigir uma permissão específica.
{
path: '/perfil',
component: () => import('pages/ProfilePage.vue'),
meta: { requiresAuth: true },
},
// Exige uma client role do ServiHub.
{
path: '/usuarios',
component: () => import('pages/UsersPage.vue'),
meta: { permission: 'servihub-usuarios-visualizar' },
},
// Autoriza quando pelo menos uma das client roles estiver presente.
{
path: '/contratos',
component: () => import('pages/ContractsPage.vue'),
meta: {
permission: [
'servihub-contratos-visualizar',
'servihub-contratos-editar',
],
permissionAny: true,
},
},
];
Uma rota com meta.permission já exige autenticação. Usuários não autenticados
são enviados ao login do Keycloak e retornam à URL original. Usuários
autenticados sem a client role são enviados para /unauthorized.
Uma realm role pode ser exigida deliberadamente com meta.realmRole. Metadados
antigos como requiredAdminLevel, requiredSuperiorLevel,
requeredSuperiorLevel e allowedRoles falham fechados e levam para
/unauthorized, para que uma regra antiga não libere acesso por engano.
Axios e respostas do backend
attachKeycloakAxios atualiza o token antes da requisição e envia:
Authorization: Bearer <access-token>
O tratamento padrão gerado é:
401: força uma renovação do token e repete a requisição uma única vez;- novo
401ou falha na renovação: abre o login do Keycloak; 403: leva para/unauthorized, preservando a rota de origem em?from=.
O backend continua sendo a autoridade final. Ocultar um botão ou proteger uma rota no navegador melhora a interface, mas não substitui a validação do token e das client roles na API.
Uso manual:
import { attachKeycloakAxios, getKeycloak } from 'pmvc-vue-permissions/quasar';
const detach = attachKeycloakAxios(api, {
getKeycloak,
onForbidden: () => router.replace('/unauthorized'),
onUnauthorized: (_error, keycloak) => keycloak.login(),
});
// Use quando a instância Axios deixar de existir.
detach();
Diretiva e composable
O KeycloakBoot registra o plugin Vue automaticamente no scaffold gerado.
Para adotar a convenção prefixo-módulo-ação, configure o contrato uma única
vez no boot que já existe na aplicação:
export default KeycloakBoot({
config: {
url: process.env.KEYCLOAK_URL,
realm: process.env.KEYCLOAK_REALM,
clientId: process.env.KEYCLOAK_CLIENT_ID,
},
permissionContract: { prefix: 'conecta' },
getAuthStore: ({ store }) => useKeycloakAuthStore(store),
});
initializeKeycloakVue aceita a mesma opção em aplicações Vue/Vite. O prefixo
deve representar exatamente os nomes cadastrados como client roles; ele não é
inferido do client ID.
Com o contrato configurado, a própria diretiva combina módulo e ação. Não é
necessário criar um permissions.js no projeto consumidor:
<template>
<q-btn
v-pmvc-permission:usuarios="'editar'"
label="Editar"
/>
<q-btn
v-pmvc-permission:usuarios.any="['adicionar', 'editar']"
label="Salvar"
/>
</template>
O primeiro exemplo verifica conecta-usuarios-editar. O segundo autoriza se
ao menos uma das permissões geradas estiver presente. Também é possível usar o
objeto explícito v-pmvc-permission="{ module: 'usuarios', action: 'editar' }".
Nomes completos continuam compatíveis, inclusive durante uma migração:
<template>
<q-btn
v-pmvc-permission="'servihub-usuarios-editar'"
label="Editar"
/>
<q-btn
v-pmvc-permission.any="[
'servihub-usuarios-adicionar',
'servihub-usuarios-editar',
]"
label="Salvar"
/>
</template>
A diretiva remove o elemento do DOM quando o acesso é negado e o restaura na
mesma posição quando a permissão é concedida. Isso reproduz a presença visual de
um v-if, mas, por limitação das diretivas customizadas do Vue, o setup() de
um componente ainda é executado. Para impedir também a criação da instância, use
o composable:
<script setup>
import { usePmvcPermissions } from 'pmvc-vue-permissions';
const { can, permission } = usePmvcPermissions();
</script>
<template>
<UserEditor v-if="can({ module: 'usuarios', action: 'editar' })" />
</template>
permission('usuarios', 'editar') devolve o nome completo quando uma string é
necessária dentro do setup().
Para uso independente do plugin — como em um módulo de rotas — importe o construtor diretamente:
import { createPermissionContract } from 'pmvc-vue-permissions';
const permissions = createPermissionContract({ prefix: 'conecta' });
permissions.permission('usuarios', 'editar');
// conecta-usuarios-editar
permissions.filter(token.resource_access?.[clientId]?.roles);
// somente roles que começam com conecta-, sem duplicatas
Store Keycloak e contexto local
O gerador cria uma store separada da store de domínio para não sobrescrever dados locais durante uma migração:
const auth = useKeycloakAuthStore();
auth.isAuthenticated;
auth.subject; // token.sub
auth.nameUser;
auth.permissions; // client roles do KEYCLOAK_CLIENT_ID
auth.realmRoles; // separadas
auth.can('servihub-usuarios-visualizar');
auth.localContext;
Quando a aplicação precisa buscar usuário local, contrato, secretaria ou outros
dados depois do login, use onAuthenticated:
export default KeycloakBoot({
config: {
url: process.env.KEYCLOAK_URL,
realm: process.env.KEYCLOAK_REALM,
clientId: process.env.KEYCLOAK_CLIENT_ID,
},
getAuthStore: ({ store }) => useKeycloakAuthStore(store),
onAuthenticated: async ({ auth }) => {
const { data } = await api.get('/v1/users/me');
auth.setLocalContext(data);
},
});
O contexto local pode orientar a experiência e selecionar registros. A autorização continua baseada nas client roles e deve ser confirmada no backend.
Logout
Todos os pontos de saída devem usar a store Keycloak e aguardar o redirecionamento:
await keycloakAuthStore.logout({
redirectUri: `${window.location.origin}/login`,
});
Não limpe apenas a store JWT seguida de router.push('/login'): isso mantém a
sessão SSO no Keycloak e o próximo check-sso autentica novamente. Além das
redirect URIs de login e web origins, cadastre o destino em Valid post logout
redirect URIs no cliente Keycloak.
Migração de uma autenticação existente
A CLI reconhece o formato antigo usado na branch Keycloak do ServiHub:
- instância direta de
keycloak-jsno plugin; - boot com
keycloak.init()elogin-required; - interceptor Axios manual;
router.beforeEachbaseado emadmin,supervisorou metadados antigos.
Quando reconhece esses arquivos, instala a integração nova diretamente, sem
gerar cópias *.legacy.js. Arquivos customizados que não correspondem aos
padrões conhecidos são preservados e a CLI exibe um aviso.
O relatório permissions.todo.md localiza usos restantes de isAdmin,
access_level, allowedRoles, login local, renovação manual do token e consultas
diretas de role. Ele não inventa o mapeamento funcional: cada ocorrência deve
receber a client role correta do ServiHub.
Depois do init:
- revise os backups e o relatório;
- troque decisões genéricas de papel por
meta.permission,v-pmvc-permissionoucan(); - remova inicialização e intervalos de renovação antigos apontados no relatório;
- teste páginas públicas, login, retorno à rota, 401 e 403;
- confirme as mesmas client roles no backend.
O comando é idempotente: arquivos já gerados são preservados e backups não são sobrescritos.
Configuração no Keycloak
O cliente do frontend deve ser público, usar Authorization Code com PKCE e ter
redirect URIs e web origins correspondentes aos endereços da aplicação. As
client roles do ServiHub precisam aparecer no access token em
resource_access[clientId].roles.
Variáveis geradas:
KEYCLOAK_URL=https://keycloak.exemplo.gov.br
KEYCLOAK_REALM=PMVC
KEYCLOAK_CLIENT_ID=servihub
Não coloque client secret no frontend.
Uso sem a CLI
A parte de diretiva é independente de Quasar e Keycloak:
import { createApp } from 'vue';
import { PmvcPermissions } from 'pmvc-vue-permissions';
createApp(App).use(PmvcPermissions, {
permissionContract: { prefix: 'conecta' },
hasPermission: name => auth.permissions.includes(name),
});
Também é possível registrar somente a diretiva com
createPermissionDirective({ hasPermission }).
API pública
Entrada pmvc-vue-permissions:
PmvcPermissions;createPermissionContract;createPermissionDirective;usePmvcPermissions.
Entrada pmvc-vue-permissions/quasar:
KeycloakBoot;getKeycloakekeycloak;extractKeycloakAccess;hasPermission;attachKeycloakAxios;createKeycloakNavigationGuard.
Desenvolvimento e publicação
npm ci
npm run check
npm pack --dry-run
npm publish --access public
A versão atual é 0.5.0. O prepack executa build, testes de comportamento e
validação TypeScript. A licença atual é UNLICENSED.
Related Packages
Nuxt directives (`v-can`, `v-cannot`) to layer permissions without touching busi...
Version History
| Version | Released | License |
|---|---|---|
| 0.5.1 | Proprietary | |
| 0.5.0 | Proprietary | |
| 0.4.4 | Proprietary | |
| 0.4.3 | Proprietary | |
| 0.4.2 | Proprietary | |
| 0.4.1 | Proprietary | |
| 0.4.0 | Proprietary | |
| 0.3.8 | Proprietary | |
| 0.3.3 | Proprietary | |
| 0.3.2 | Proprietary | |
| 0.3.1 | Proprietary | |
| 0.3.0 | Proprietary |