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 doif (res.success), o TypeScript sabe quedataexiste. 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')eapi.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). Um401invalida 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
requireeimportfuncionam em qualquer Node 20+, inclusive Jest em modo CommonJS, sem depender dorequire(esm). - Tipos gerados do OpenAPI oficial.
User,Client,Realm,Groupetc. vêm direto da spec do Keycloak 26.8.1, e os schemas brutos estão disponíveis emKeycloakSchemas. createdevolve o id. O Keycloak responde201com o id no headerLocation, e a lib entrega esse id emdata.
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 exigekeyPassword. - 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
- npm: https://www.npmjs.com/package/keycloak-api-ts
- GitHub: https://github.com/Master-Mils/keycloak-api-ts
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.
