npm.io
0.1.5 • Published 23h ago

@tjpa/n8n-nodes-keycloak-guard

Licence
MIT
Version
0.1.5
Deps
2
Size
217 kB
Vulns
0
Weekly
0

@tjpa/n8n-nodes-keycloak-guard

Community node para n8n com propósito único: proteger webhooks do n8n com Keycloak. O login do usuário acontece inteiramente fora do n8n (keycloak-js + Authorization Code Flow com PKCE, no browser); este pacote apenas injeta esse login no HTML servido e valida o token nas chamadas de volta.

É uma reescrita enxuta e endurecida do n8n-nodes-keycloak (MIT), do qual este projeto herda a licença e créditos.

Nodes

  • Keycloak Guard — injeta o login Keycloak + um interceptor de fetch/XMLHttpRequest numa página HTML.
  • Keycloak Verify — valida o token enviado pelo interceptor e libera o fluxo do workflow.

Credencial: Keycloak Guard API

Uma única credencial, tipo keycloakGuardApi, com 5 campos:

Campo Uso
serverUrl URL base do Keycloak
realm Realm
frontendClientId Client público (Standard Flow + PKCE), vai para o browser
backendClientId Client confidential, usado só no server (Keycloak Verify)
backendClientSecret Secret do client confidential. Nunca é injetado no HTML.

O credential test bate em .well-known/openid-configuration do realm configurado.

serverUrl é sempre normalizado antes de montar qualquer endpoint — tanto faz digitar com barra final, com /realms ou /realms/ no fim: tudo resolve para a mesma URL.

Fluxo completo

[Webhook GET] -> [Node HTML gera a página] -> [Keycloak Guard injeta login] -> [Respond to Webhook]

  no browser: o usuário loga no Keycloak (fora do n8n)
  chamadas JS da página passam pelo interceptor injetado, que anexa o Bearer token

[Webhook API] -> [Keycloak Verify] -> authorized -> resto do workflow usa item.json.user
                                    -> unauthorized
                                    -> error
Configuração dos 2 clients no Keycloak
  1. Client público (ex.: n8n-frontend): Standard Flow habilitado, PKCE S256, Valid Redirect URIs = URL da página servida pelo webhook, Web Origins = origin do n8n.
  2. Client confidential (ex.: n8n): usado só pelo Keycloak Verify para introspection.

Keycloak Guard

Parâmetros:

  • HTML Field (htmlField, default html) — nome do campo em item.json que contém a string HTML a modificar (nem sempre se chama html).
  • Keycloak JS URL (keycloakJsUrl) — default aponta pro build ESM (lib/keycloak.js) do keycloak-js@26 no CDN jsdelivr. A partir da v26 o pacote só é distribuído como módulo ES (sem bundle UMD/global em dist/), por isso o script injetado usa <script type="module"> + import. Se sua rede não tiver saída pra CDN externo, hospede esse arquivo internamente e aponte esse campo pra lá.
  • Allowed API Origins (apiOrigins, CSV) — origens além do mesmo-origin que também podem receber o header Authorization. Qualquer outro destino nunca recebe o token do usuário.

O node injeta, logo antes de </body> (ou no final da string, se não houver </body>):

  1. <script type="module"> com import Keycloak from "<keycloakJsUrl>".
  2. Inicialização: new Keycloak({ url, realm, clientId: frontendClientId }) com onLoad: 'login-required', pkceMethod: 'S256', checkLoginIframe: false. Sem sessão, redireciona para o login oficial do Keycloak; só segue com o usuário autenticado.
  3. Um interceptor global de window.fetch e XMLHttpRequest.prototype.send que, antes de cada requisição, chama keycloak.updateToken(30) (refresh se o token estiver perto de expirar) e só anexa Authorization: Bearer <token> se o destino for o mesmo origin da página ou estiver na allowlist de apiOrigins.

O node só lê serverUrl, realm e frontendClientId da credencial — nunca referencia backendClientId/backendClientSecret, garantindo que o secret jamais vá parar no HTML.

Keycloak Verify

Parâmetros:

  • Token Key (tokenKey, default headers.authorization) — caminho (dot path) dentro de item.json onde está o token. O prefixo Bearer é removido automaticamente, case-insensitive. A extração é feita por item, dentro do loop — diferente do node original, que tinha um bug de ler o token só do primeiro item e validar todos os outros com ele.
  • Verify Mode (verifyMode):
    • introspection (default): POST em ${base}/realms/${realm}/protocol/openid-connect/token/introspect, com Content-Type: application/x-www-form-urlencoded e Authorization: Basic base64(backendClientId:backendClientSecret). Se active=false, o item vai para unauthorized. Pega tokens revogados.
    • offline: valida a assinatura localmente via JWKS (${base}/realms/${realm}/protocol/openid-connect/certs), com cache (TTL configurável, default 1h) e checagem de exp/iss. Mais rápido, porém não detecta revogação — se isso importa para o seu caso de uso, prefira introspection.
  • JWKS Cache TTL (Seconds) — só aparece com verifyMode = offline.
  • Required Realm Roles, Required Client Roles (client:role), Required Groups — gates opcionais, checados contra realm_access.roles, resource_access[client].roles e groups do token. Faltando algum, o item vai para unauthorized.

Independentemente do modo escolhido, o JWT é sempre decodificado para extrair as claims — introspection decide se o token está active, o decode do JWT popula os dados do usuário (a introspection nem sempre retorna todas as claims).

3 saídas: authorized / unauthorized / error (erro de rede, JWKS inacessível, etc.).

No item que sai por authorized, é anexado item.json.user enxuto:

{
  "sub": "...",
  "preferred_username": "...",
  "name": "...",
  "given_name": "...",
  "family_name": "...",
  "email": "...",
  "cpf": "...",
  "groups": ["..."],
  "roles": ["..."],
  "membership_unit_ref": "..." 
}

(membership_unit_ref só aparece se presente no token.) O token completo nunca é anexado ao item, e nem o token nem o secret são logados em nenhum momento.

Configuração via CREDENTIALS_OVERWRITE_DATA

A credencial é preenchida globalmente pelo ambiente do n8n — no editor, o usuário só cria a credencial vazia (os campos ficam ocultos) e seleciona. Não usa prefixo N8N_:

{
  "keycloakGuardApi": {
    "serverUrl": "https://sso.tjpa.jus.br",
    "realm": "tjpa",
    "frontendClientId": "n8n-frontend",
    "backendClientId": "n8n",
    "backendClientSecret": "<secret>"
  }
}

Para o secret, prefira CREDENTIALS_OVERWRITE_DATA_FILE (arquivo montado read-only) em vez de CREDENTIALS_OVERWRITE_DATA inline — a documentação do n8n avisa que variáveis de ambiente podem vazar para os usuários (ex.: em nodes que exibem env vars).

Testando localmente

npm run build

Copie as pastas resultantes de dist/ para ~/.n8n/custom/ do container e reinicie o n8n.

Distribuição via GitLab Package Registry

  1. Publicar com o publishConfig do package.json já apontando para o Project Package Registry deste projeto (id 85627349):
    "publishConfig": {
      "@tjpa:registry": "https://gitlab.com/api/v4/projects/85627349/packages/npm/"
    }
    O pipeline em .gitlab-ci.yml publica automaticamente ao criar uma tag vX.Y.Z, usando ${CI_JOB_TOKEN} (não precisa cadastrar token manualmente). Há também um job manual publish_npm que publica a mesma tag no npmjs.com público, usando a variável de CI NPM_TOKEN.
  2. Instalar via imagem Docker customizada, com .npmrc mapeando o scope @tjpa para o registry do grupo, ENV N8N_CUSTOM_EXTENSIONS=/home/node/custom-nodes e pnpm install (o n8n usa pnpm; npm install -g não funciona mais nas imagens atuais). Veja docker/Dockerfile e docker/.npmrc.example.

Kubernetes em queue mode

A execução dos nodes roda nos workers, não no main — a imagem com este pacote precisa estar presente no main e em todos os workers. Ao atualizar a versão, é preciso "rolar" (rolling restart) todos os pods. O CREDENTIALS_OVERWRITE_DATA/_FILE também precisa estar presente em todos os pods (main + workers), senão a credencial fica vazia em parte deles.

Testes

npm test

Cobre: normalização de serverUrl em todas as variações aceitas; injeção antes de </body> (incluindo case-insensitive) e fallback de append quando não há </body>; ausência de backendClientId/backendClientSecret no HTML gerado; extração de token com/sem Bearer case-insensitive; extração independente por item (regressão do bug do node original); active=false sempre resultando em unauthorized; gates de role/grupo autorizando e negando; e o formato enxuto do user na saída authorized.

Licença

MIT. Ver LICENSE. Este projeto é uma reescrita do n8n-nodes-keycloak, também MIT.

Keywords