Pitch: Keycloak Admin API em TypeScript sem sustos: lancei a keycloak-api-ts 2.0

Quem automatiza o Keycloak a partir do Node.js (criar usuários em onboarding, provisionar clients por tenant, sincronizar grupos com o RH) cedo ou tarde esbarra no mesmo conjunto de atritos. Escrevi a keycloak-api-ts para resolver os que mais me incomodavam, e a versão 2.0 acabou de sair: cobre 100% da Admin API do Keycloak 26.8.1 (415 operações) e é testada contra servidores reais 24, 25 e 26 em todo PR.

Este post mostra o problema, a solução e os trade-offs.

O problema

O client oficial, @keycloak/keycloak-admin-client, é mantido pelo time do Keycloak e é a escolha natural. Mas, em código de backend do dia a dia (olhando a versão 26.8.1), ele cobra alguns pedágios:

1. É ESM-only. O pacote é "type": "module", sem build CommonJS. Em Node 20.19+ e 22.12+ o require() de ESM já funciona, mas Jest/ts-jest em modo CommonJS, Node mais antigo e alguns bundlers ainda tropeçam e pedem transformIgnorePatterns, await import() ou migrar o projeto.

2. Erro HTTP vira exceção. Um usuário que não existe (404) ou um username duplicado (409) não são situações excepcionais, são regra de negócio. Mesmo assim, toda chamada lança NetworkError quando o status não é 2xx (só alguns findOne devolvem null no 404), então você precisa de try/catch e de inspecionar error.response.status.

// client oficialtry { await kc.users.create({ username: 'jdoe' });} catch (e) { // é 409? é 403? é rede? depende de inspecionar o erro}

3. O realm padrão é estado global mutável. kc.setConfig({ realmName: 'tenant-a' }) muda o realm de todas as chamadas seguintes. Dá para sobrescrever por chamada passando realm no payload, mas é opcional: esqueceu em um lugar, e em código multi-tenant concorrente a chamada cai no realm que outra requisição configurou.

4. Token: renova só com refresh token. Depois do auth(), o client renova sozinho se houver refresh token. Mas com client_credentials, o fluxo típico de serviço, o Keycloak não emite refresh token por padrão (a opção "Use refresh tokens for client credentials grant" do client vem desligada), e quando o access token expira a chamada falha com "Cannot refresh token". A saída é chamar auth() de novo ou registrar um TokenProvider próprio. Não há retry em 401, nem retry com backoff, nem rate limit.

5. A cobertura é parcial. Pela minha contagem na 26.8.1, o client oficial cobre cerca de 340 das 415 operações da spec OpenAPI (≈82%). Ficam de fora, por exemplo, composites, grupos e permissões de roles pelo nome, buscas de authorization services, client types, parte de organizations e workflows, localização e os client templates legados. Nesses casos, você volta a montar fetch na mão.

A solução

npm install keycloak-api-ts

import KeycloakAPI from 'keycloak-api-ts'; // ou require('keycloak-api-ts')const api = new KeycloakAPI({ baseUrl: 'https://sso.example.com', credentials: { grantType: 'client_credentials', clientId: 'admin-service', clientSecret: process.env.KEYCLOAK_CLIENT_SECRET, },});const created = await api.users.create('acme', { username: 'jdoe', enabled: true });if (created.success) { console.log('id do novo usuário:', created.data); // extraído do header Location} else if (created.status === 409) { console.log('usuário já existe');} else { console.error(created.status, created.error);}

O que muda:

  • Erros como valores. Toda chamada resolve para { success, status, data | error }, uma union discriminada: depois do if (res.success), o TypeScript sabe que data existe. A Promise só rejeita em falha de rede ou quando não dá para obter o token (KeycloakAuthError).
  • Realm por chamada. api.users.list('tenant-a') e api.users.list('tenant-b') podem rodar em paralelo na mesma instância sem interferência.
  • Token automático. É pedido sob demanda, fica em cache e é renovado antes do expires_in (com refresh token, se houver). Um 401 invalida o token e repete a requisição uma vez.
  • Resiliência na instância. Retry com backoff exponencial para erros de rede, 408, 429 e 5xx em requisições idempotentes, e um rate limit no cliente (10 req/s por padrão). Tudo aplicado num axios próprio, sem tocar no axios global de quem importa a lib.
  • CommonJS e ESM nativos. Dois builds no pacote, então require e import funcionam em qualquer Node 20+, inclusive Jest em modo CommonJS, sem depender do require(esm).
  • Tipos gerados do OpenAPI oficial. User, Client, Realm, Group etc. vêm direto da spec do Keycloak 26.8.1, e os schemas brutos estão disponíveis em KeycloakSchemas.
  • create devolve o id. O Keycloak responde 201 com o id no header Location, e a lib entrega esse id em data.

O que só aparece testando contra um servidor de verdade

A spec OpenAPI não conta tudo. Algumas coisas só descobri rodando os testes de integração (Testcontainers subindo o Keycloak em Docker):

  • Policies de authorization precisam ser criadas em /policy/{type}; o endpoint genérico da spec não consegue interpretar os campos do provider.
  • Textos de localização, labels de credencial e workflows usam corpo texto ou YAML, não JSON.
  • Download de keystore volta como Buffer, e na 24/25 exige keyPassword.
  • Upload de certificado precisa ser multipart.
  • Filtros em array vão como parâmetros repetidos (type=LOGIN&type=LOGOUT).

Cada uma dessas virou teste, e o CI roda a matriz 24.0.5, 25.0.6 e 26.8.1 em todo PR.

Quando usar o client oficial

Se o seu projeto já é ESM, roda sempre na última versão do Keycloak e você prefere exceções, o client oficial é uma escolha perfeitamente boa e tem o respaldo do time do Keycloak. Ele também já traz coisas que a keycloak-api-ts ainda não tem: endpoints de Shared Signals (SSF) e verifiable credentials, que nem estão na spec OpenAPI, e um acesso experimental à Admin API v2 de clients. A keycloak-api-ts faz mais sentido quando você tem backend CommonJS, código multi-tenant, servidores em versões diferentes (24 a 26) ou precisa de endpoints que o oficial não cobre.

Links

Issues, sugestões e PRs são bem-vindos. Os próximos passos do roadmap são a Admin API v2, assim que ela estabilizar, e os endpoints de SSF e verifiable credentials. Se você usa Keycloak com Node, me conta nos comentários qual é o seu maior atrito com a Admin API.

Source

2 views·1 share