Keycloak como Middleware de Identidade
O Wittgenstein não implementa login, armazenamento de senhas nem federação por conta própria. Ele coloca o Keycloak entre cada usuário, cada aplicação e cada fonte de identidade externa, e o trata como middleware de identidade: as aplicações falam um único protocolo (OIDC) com um único lugar, e o Keycloak se adapta ao que cada organização já usa.
flowchart LR
subgraph Sources["Fontes de identidade"]
G[IdP social / workspace]
E[IdP corporativo OIDC ou SAML]
L[LDAP / Active Directory]
P[Usuários locais + MFA]
end
KC(["Keycloak<br/>um realm por tenant"])
subgraph Consumers["Consumidores"]
A[Aplicações web<br/>OIDC + PKCE]
S[Serviços e agentes<br/>client credentials]
T[Ferramentas<br/>SAML / OIDC]
end
G --> KC
E --> KC
L --> KC
P --> KC
KC --> A
KC --> S
KC --> T
KC -. JWKS .-> GW[AgentGateway<br/>+ extProc]
GW --> A
Por que um middleware, e não uma biblioteca
| Necessidade | Resolvido pelo Keycloak | Efeito na aplicação |
|---|---|---|
| "Nossos usuários entram com o IdP corporativo" | Identity brokering (OIDC / SAML) | Nenhum — a app continua recebendo um token Keycloak |
| "Já temos um diretório" | Federação LDAP / Active Directory | Nenhum |
| "Entrar com uma conta de workspace" | IdP social / workspace, opcionalmente restrito a um domínio | Nenhum |
| "Exigir segundo fator para administradores" | Fluxos de autenticação, MFA | Nenhum |
| "Clientes diferentes nunca podem ver os usuários uns dos outros" | Um realm por tenant | Apontar a app para outro authority |
| "Máquinas e agentes também precisam de tokens" | Clients de service account (client_credentials) |
Mesmo caminho de verificação de JWT |
| "Ferramentas prontas precisam de SSO" | Clients SAML e OIDC | Configurado uma vez no realm |
O código da aplicação é idêntico em todas essas linhas. É isso que permite a uma única plataforma atender clientes com cenários de identidade muito diferentes sem criar forks.
Modelo de tenancy
- Um realm por tenant quando é necessário isolar usuários, políticas e identidade visual. Cada realm tem seus próprios usuários, IdPs, política de MFA, tempos de vida de token e chaves de assinatura.
- Um client por aplicação dentro do realm, com o menor conjunto possível de redirect URIs e scopes.
- Papéis e grupos conduzem a autorização. Os papéis do realm chegam em
realm_access.rolese os grupos no claimgroups; owittgenstein-oidc-backendos expõe porget_roleseget_groups, e@has_role([...])os aplica por rota.
from wittgenstein_oidc_backend import get_current_payload, get_groups, get_roles
payload = get_current_payload()
roles = get_roles(payload) # ["manager", ...]
groups = get_groups(payload) # ["/customers/acme", ...]
O significado de domínio fica no seu serviço
A biblioteca extrai claims de forma genérica. Interpretar o nome de um grupo (por exemplo, mapeá-lo para uma conta de cliente) é regra de negócio da sua aplicação, não da plataforma.
Tipos de client e grants
| Client | Tipo | Grant | Observações |
|---|---|---|---|
| Aplicação no navegador | Confidencial via backend (ou público + PKCE) | Authorization Code + PKCE | O backend guarda o secret; veja OIDC e Segurança de Tokens |
| Backend / agente chamando outro serviço | Confidencial | client_credentials |
Service account com papéis estritamente necessários |
| Automação administrativa | Confidencial | client_credentials |
Somente os papéis de realm-management de que precisa |
| Ferramenta de terceiros | SAML ou OIDC | Conforme a ferramenta | Registrada uma vez por realm |
Os direct access grants (password grant) ficam desabilitados nos clients de navegador, de modo que credenciais só podem ser digitadas nas páginas do próprio Keycloak, onde valem o MFA e a proteção contra força bruta.
Mapeando identidades para a sua aplicação
- Claims — protocol mappers adicionam ao token os papéis, grupos e atributos de que seus serviços precisam.
- Provisionamento JIT — no primeiro login,
jit_provision_usercria o usuário local, vincula osubdo Keycloak e sincroniza os papéis a cada login seguinte. - Cabeçalhos do gateway — atrás do AgentGateway, o sidecar
extProcvalida o JWT contra o JWKS e injetax-user-idex-user-name, de modo que serviços que só precisam saber "quem está chamando" nunca precisam interpretar um token.
Realms automatizados e repetíveis
Os realms são criados por scripts idempotentes em vez de configurados manualmente: rodar o setup de novo cria o que falta e atualiza o que divergiu (identity providers sofrem upsert, não apenas criação). Um novo realm de tenant é, portanto, uma mudança revisada, e não um procedimento manual (a saída abaixo está abreviada).
[keycloak] identity provider 'workspace' ... created
[keycloak] client 'acme-app' ... created (confidential, PKCE S256)
[keycloak] client 'acme-service' ... created (service account)
[keycloak] realm role 'manager' ... created./setup-keycloak.sh[keycloak] realm 'acme' ... exists, skipped
[keycloak] identity provider 'workspace' ... updated
[keycloak] client 'acme-app' ... exists, skipped
Operando o Keycloak
- TLS — terminado pelo AgentGateway (o provedor de identidade roda atrás dele em modo edge proxy) ou pelo próprio Keycloak em um host dedicado, com certificados emitidos e renovados automaticamente.
- Armazenamento — um banco PostgreSQL dedicado; nada mais o compartilha.
- Descoberta — todo realm publica
/.well-known/openid-configuration; nenhum endpoint é fixado no código das aplicações.
"issuer": "https://auth.example.com/realms/acme",
"jwks_uri": "https://auth.example.com/realms/acme/protocol/openid-connect/certs",
"token_endpoint": "https://auth.example.com/realms/acme/protocol/openid-connect/token"
}curl -s -X POST https://auth.example.com/realms/acme/protocol/openid-connect/token \ -d grant_type=client_credentials -d client_id=acme-service -d client_secret="$SECRET" | jq 'keys'[
"access_token",
"expires_in",
"token_type"
]
Adicionando um novo tenant
Novo realm → conectar o IdP dele (ou usar usuários locais com MFA) → criar o client da aplicação → dar à app o novo authority. Nenhuma mudança no código da aplicação.