Internal-Trusted Authentication
Visão Geral
Internal-Trusted Authentication é um padrão de autenticação utilizado para comunicação segura entre serviços internos da arquitetura Santa Mão. Este padrão permite que sistemas confiáveis (workers, background services, APIs internas) se autentiquem sem intervenção do usuário.
Tecnologia utilizada: Keycloak - Plataforma open-source de gerenciamento de identidade e acesso.
Conceitos Fundamentais
O que é Internal-Trusted?
Internal-Trusted é um modelo de autenticação onde serviços internos da aplicação são considerados "confiáveis" e podem obter tokens de acesso automaticamente para se comunicar com outros serviços.
Características:
- Sem interação do usuário: Autenticação automática entre serviços
- Client Credentials Flow: Utiliza OAuth 2.0 Client Credentials Grant
- Service-to-Service: Comunicação máquina-para-máquina
- Centralizado: Gerenciado pelo Keycloak
Keycloak no Santa Mão
Keycloak é uma solução completa de Identity and Access Management (IAM) que fornece:
- Single Sign-On (SSO): Login único entre aplicações
- Identity Brokering: Integração com provedores externos (Google, Facebook, etc)
- User Federation: Integração com LDAP/Active Directory
- OAuth 2.0 / OpenID Connect: Protocolos padrão de autenticação
- Fine-grained Authorization: Controle granular de permissões
URL do Keycloak (Desenvolvimento):
https://auth.santamao.com.br
Client Credentials Flow

Fluxo:
- Serviço envia
client_ideclient_secretpara o Keycloak - Keycloak valida as credenciais
- Keycloak retorna um
access_tokenJWT - Serviço usa o token no header
Authorization: Bearer {token} - API valida o token com o Keycloak
Implementação no Santa Mão
Configuração do Keycloak
Realm: santamao
Clients Internos:
| Client ID | Descrição | Uso |
|---|---|---|
notification-worker |
Worker de notificações | Envia emails, SMS e push |
payment-worker |
Worker de pagamentos | Processa pagamentos |
analytics-worker |
Worker de analytics | Processa dados analíticos |
image-worker |
Worker de imagens | Processa uploads de imagens |
cache-worker |
Worker de cache | Gerencia invalidação de cache |
internal-api |
API interna de administração | Operações administrativas |
Configuração de um Client no Keycloak
1. Criar Client
Name: notification-worker
Client Protocol: openid-connect
Access Type: confidential
Service Accounts Enabled: ON
Authorization Enabled: OFF
2. Obter Credenciais
Client ID: notification-worker
Client Secret: [gerado automaticamente pelo Keycloak]
3. Configurar Roles
Roles do Client:
- send-notification
- read-user-preferences
- write-notification-log
Implementação em .NET
appsettings.json:
{
"Keycloak": {
"Authority": "https://auth.santamao.com.br/realms/santamao",
"TokenEndpoint": "https://auth.santamao.com.br/realms/santamao/protocol/openid-connect/token",
"ClientId": "notification-worker",
"ClientSecret": "your-client-secret-here",
"Audience": "santamao-api"
}
}
Obter Token (Worker):
public class KeycloakTokenService : IKeycloakTokenService
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
private string? _cachedToken;
private DateTime _tokenExpiration;
public async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken = default)
{
// Verificar se token em cache ainda é válido
if (!string.IsNullOrEmpty(_cachedToken) && DateTime.UtcNow < _tokenExpiration)
{
return _cachedToken;
}
var tokenEndpoint = _configuration["Keycloak:TokenEndpoint"];
var clientId = _configuration["Keycloak:ClientId"];
var clientSecret = _configuration["Keycloak:ClientSecret"];
var requestBody = new Dictionary<string, string>
{
{ "grant_type", "client_credentials" },
{ "client_id", clientId },
{ "client_secret", clientSecret }
};
var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
{
Content = new FormUrlEncodedContent(requestBody)
};
var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
var tokenResponse = JsonSerializer.Deserialize<TokenResponse>(content);
_cachedToken = tokenResponse.AccessToken;
_tokenExpiration = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn - 60); // 60s de margem
return _cachedToken;
}
}
public class TokenResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
}
Chamar API com Token:
public class NotificationApiClient
{
private readonly HttpClient _httpClient;
private readonly IKeycloakTokenService _tokenService;
public async Task SendNotificationAsync(NotificationDto notification)
{
var token = await _tokenService.GetAccessTokenAsync();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.PostAsJsonAsync(
"https://api.santamao.com.br/v1/notifications",
notification);
response.EnsureSuccessStatusCode();
}
}
Validar Token (API):
// Program.cs ou Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = configuration["Keycloak:Authority"];
options.Audience = configuration["Keycloak:Audience"];
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("InternalTrusted", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("azp"); // Authorized party (client_id)
});
options.AddPolicy("NotificationWorker", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("azp", "notification-worker");
});
});
Proteger Endpoint:
[ApiController]
[Route("api/v1/notifications")]
[Authorize(Policy = "InternalTrusted")] // Qualquer serviço interno
public class NotificationsController : ControllerBase
{
[HttpPost]
[Authorize(Policy = "NotificationWorker")] // Apenas notification-worker
public async Task<IActionResult> SendNotification([FromBody] NotificationDto notification)
{
var clientId = User.FindFirst("azp")?.Value; // notification-worker
await _notificationService.SendAsync(notification);
return Ok();
}
}
Benefícios
✅ Segurança - Autenticação centralizada e padronizada
✅ Auditoria - Rastreamento completo de acessos por serviço
✅ Flexibilidade - Fácil adicionar/remover permissões
✅ Escalabilidade - Suporta múltiplas instâncias de workers
✅ Padrão de Mercado - OAuth 2.0 / OpenID Connect
✅ Gerenciamento Centralizado - Interface web do Keycloak
✅ Token Caching - Performance otimizada com cache de tokens
Quando Usar e Quando NÃO Usar
✅ Usar Internal-Trusted Quando:
- Comunicação entre serviços internos (worker → API)
- Background jobs que acessam recursos protegidos
- APIs administrativas internas
- Integração entre microserviços
- Serviços que não têm contexto de usuário
❌ NÃO Usar Internal-Trusted Quando:
- Autenticação de usuários finais (use Authorization Code Flow)
- Frontend web/mobile (use PKCE Flow)
- APIs públicas abertas
- Serviços externos não confiáveis
Segurança e Boas Práticas
1. Proteção de Credenciais
✅ Correto:
// appsettings.Production.json (não commitado)
{
"Keycloak": {
"ClientSecret": "#{KEYCLOAK_CLIENT_SECRET}#" // Variável de ambiente
}
}
❌ Incorreto:
// NÃO commitar secrets no código!
{
"Keycloak": {
"ClientSecret": "abc123-secret-hardcoded"
}
}
Usar: - Azure Key Vault - AWS Secrets Manager - Kubernetes Secrets - Variáveis de ambiente
2. Rotação de Credenciais
- Rotacionar
client_secretperiodicamente (a cada 90 dias) - Suportar múltiplos secrets simultaneamente durante transição
- Logging de quando credenciais são trocadas
3. Princípio do Menor Privilégio
✅ Correto:
notification-worker:
- send-notification
- read-user-preferences
❌ Incorreto:
notification-worker:
- admin (muito amplo!)
4. Validação de Claims
public class ValidateClientMiddleware
{
public async Task InvokeAsync(HttpContext context)
{
if (context.User.Identity?.IsAuthenticated == true)
{
var clientId = context.User.FindFirst("azp")?.Value;
var allowedClients = new[] { "notification-worker", "payment-worker" };
if (!allowedClients.Contains(clientId))
{
context.Response.StatusCode = 403;
await context.Response.WriteAsync("Client not allowed");
return;
}
}
await _next(context);
}
}
5. Token Expiration
- Tokens devem ter TTL curto (5-15 minutos)
- Implementar cache com margem de segurança
- Não armazenar tokens permanentemente
Relacionamento com Outros Padrões
Internal-Trusted trabalha em conjunto com:
- EDA - Workers autenticados consomem eventos do Redpanda e chamam APIs
- CQRS - Workers executam Commands através de APIs autenticadas
- DDD - Mantém isolamento de domínios com autenticação apropriada
Fluxo Integrado:

Troubleshooting
Problema: Worker não consegue obter token
- Verificar se
client_ideclient_secretestão corretos - Verificar se Keycloak está acessível
- Verificar se client está habilitado no Keycloak
- Verificar logs do Keycloak
Problema: API rejeita token válido
- Verificar se
Authorityestá correto na API - Verificar sincronização de relógio (NTP)
- Verificar se
Audiencecorresponde - Verificar políticas de autorização
Problema: Performance degradada
- Implementar cache de token
- Aumentar TTL do token (com cuidado)
- Usar connection pooling para HTTP requests
- Verificar latência de rede com Keycloak
Conclusão
O padrão Internal-Trusted Authentication com Keycloak no Santa Mão proporciona autenticação segura e padronizada para comunicação entre serviços internos.
Benefícios Principais:
✅ Autenticação centralizada via Keycloak
✅ Segurança robusta com OAuth 2.0
✅ Auditoria completa de acessos
✅ Facilidade de gerenciamento de permissões
✅ Escalabilidade para múltiplos serviços
A implementação consistente deste padrão garante que todos os serviços internos se comuniquem de forma segura, auditável e seguindo padrões de mercado.
Última atualização: Novembro 2025
Maintainers: Equipe de Arquitetura Santa Mão