pmvc-vue-permissions

Vue 3, Vite and Quasar client-role authorization with Keycloak bootstrap, routing, Axios and migration CLI.
1,380
Install
npm install pmvc-vue-permissions
Latest Version:0.5.1
Vue:3
License:Proprietary
Last Updated:Oct 14, 2026
Links: npm
Maintainer: francev

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.

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 com KeycloakBoot;
  • 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 401 ou 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-js no plugin;
  • boot com keycloak.init() e login-required;
  • interceptor Axios manual;
  • router.beforeEach baseado em admin, supervisor ou 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:

  1. revise os backups e o relatório;
  2. troque decisões genéricas de papel por meta.permission, v-pmvc-permission ou can();
  3. remova inicialização e intervalos de renovação antigos apontados no relatório;
  4. teste páginas públicas, login, retorno à rota, 401 e 403;
  5. 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;
  • getKeycloak e keycloak;
  • 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

@eduvia-app/nuxt-can

Nuxt directives (`v-can`, `v-cannot`) to layer permissions without touching busi...

1,214 0
@nangazaki/vue-rbac

Role-Based Access Control (RBAC) implementation for Vue.js

1,576 53
@dsb-norge/vue-keycloak-js

A Keycloak plugin for Vue >= 3.x

338,844 289
vue3-highlight-text

a vue3 directive of highlight text

84,789
@enso-ui/permissions

Basic permissions package

9,353 0

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