Introdução

Quando o assunto é autenticação e autorização em aplicações modernas, a primeira reação de muitos times é recorrer a soluções pagas de cloud — Azure Entra ID, AWS Cognito, Auth0 ou Firebase Auth. São serviços excelentes, mas carregam custos que podem surpreender à medida que a base de usuários cresce: planos por MAU (Monthly Active Users), limites de tokens, cobranças por login social e MFA. Em muitos cenários, especialmente startups, projetos pessoais, ambientes on-premises e empresas que valorizam soberania de dados, existe uma alternativa gratuita, open-source e de nível enterprise que merece toda atenção: o Keycloak.

O Keycloak é um servidor de identidade e gerenciamento de acesso mantido pela Cloud Native Computing Foundation (CNCF) como projeto incubado, originalmente criado pela Red Hat. Ele implementa os protocolos OAuth 2.0, OpenID Connect e SAML 2.0 — os mesmos utilizados por Azure Entra ID, Google e outros provedores de identidade — mas roda na sua infraestrutura, sem custos de licença, sem limites de usuários e com total controle sobre os dados. Se você já entende os fundamentos de autenticação e autorização com JWT, OAuth2 e OpenID Connect, está pronto para dar o próximo passo: colocar um identity provider completo em funcionamento com Docker ou Podman e integrá-lo à sua aplicação ASP.NET Core com C# 8.0+.

Neste artigo, vamos colocar o Keycloak para rodar em container, configurar um realm e client, integrar com uma API REST em C#, customizar a tela de login com logo e tema personalizado, e comparar com os principais provedores de identidade da cloud. O foco é prático, funcional e 100% gratuito.

💡 Dica: Embora os exemplos deste artigo utilizem C# com ASP.NET Core, o Keycloak é agnóstico de linguagem e framework. Por implementar protocolos abertos (OAuth 2.0, OpenID Connect, SAML 2.0), ele funciona com qualquer tecnologia que suporte esses padrões — incluindo Java/Spring Boot, Python/Django/Flask/FastAPI, Node.js/Express/NestJS, Go, PHP/Laravel, Ruby on Rails, Rust e Angular/React/Vue no front-end. O setup do Keycloak em container e a configuração de realm/client são idênticos independentemente da linguagem da sua aplicação.

📦 Código-fonte: A implementação completa deste artigo está no repositório blog-zocateli-sample no GitHub. Clone, explore e adapte ao seu contexto.

Por Que Escolher o Keycloak?

Custo Zero vs Provedores de Cloud

O principal argumento a favor do Keycloak é simples: não custa nada. Enquanto provedores de cloud cobram por usuário ativo ou por funcionalidade premium, o Keycloak oferece tudo gratuitamente:

FuncionalidadeKeycloak (Gratuito)Azure Entra IDAWS CognitoAuth0
Custo base$0Grátis até 50.000 MAU (External ID)Grátis até 50.000 MAUGrátis até 7.500 MAU
Usuários ilimitados❌ (cobra após limite)❌ (cobra após limite)❌ (cobra após limite)
Login social✅ Ilimitado✅ (limitado no free)
MFA (Multi-Factor)✅ Incluído✅ (custo adicional SMS)✅ (limitado no free)
SAML + OIDC✅ Ambos✅ Ambos✅ OIDC / ⚠️ SAML limitado✅ Ambos
Customização de UI✅ Total (temas FreeMarker)⚠️ Limitada⚠️ Limitada✅ (planos pagos)
Self-hosted✅ Obrigatório❌ SaaS only❌ SaaS only⚠️ Plano Enterprise
Soberania dos dados✅ Total❌ Dados na Microsoft❌ Dados na AWS❌ Dados na Auth0

💡 Dica: O Keycloak não substitui necessariamente provedores de cloud em todos os cenários. Se sua empresa já está no ecossistema Azure e precisa de integração nativa com Microsoft 365, Teams e Entra ID, o Azure Entra continua sendo a melhor escolha. O Keycloak brilha quando você precisa de controle total, custo previsível (zero) e independência de vendor.

Principais Funcionalidades

O Keycloak inclui funcionalidades que em provedores de cloud são cobradas separadamente:

  • Single Sign-On (SSO) — login único entre múltiplas aplicações
  • Identity Brokering — federação com provedores externos (Google, GitHub, Facebook, Azure AD, SAML)
  • User Federation — integração com LDAP e Active Directory
  • Fine-grained Authorization — políticas de autorização baseadas em roles, groups, scopes e resources
  • Multi-Factor Authentication (MFA) — TOTP, WebAuthn/FIDO2, SMS, email
  • Account Management Console — portal self-service para o usuário gerenciar perfil, sessões e 2FA
  • Admin Console — painel web completo para gerenciar realms, clients, users e roles
  • Event Logging e Audit Trail — registro detalhado de eventos de autenticação
  • API REST para Administração — automação completa via API
  • Extensibilidade via SPI — Service Provider Interfaces para customizações avançadas

Pré-requisitos

  • Docker Desktop ou Podman instalado e funcionando
  • .NET 8.0 SDK (ou superior) instalado — download aqui
  • VS Code ou Visual Studio como IDE
  • Conhecimento básico de OAuth2 e OpenID Connect

Keycloak em Container: Setup Completo

Docker Compose para Desenvolvimento

A forma mais rápida e recomendada de rodar o Keycloak localmente é via container. Vamos usar o docker-compose com PostgreSQL como banco de dados (o mesmo banco que você usaria em produção):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# docker-compose.keycloak.yml
services:
  keycloak-postgres:
    image: postgres:16-alpine
    container_name: keycloak-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: keycloak-dev-123
      TZ: America/Sao_Paulo
    volumes:
      - keycloak-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    container_name: keycloak
    restart: unless-stopped
    environment:
      # Admin inicial
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: admin
      # Banco de dados
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: keycloak-dev-123
      # Configurações
      KC_HOSTNAME: localhost
      KC_HTTP_ENABLED: "true"
      KC_HOSTNAME_STRICT: "false"
      KC_PROXY_HEADERS: xforwarded
      TZ: America/Sao_Paulo
    ports:
      - "8443:8443"
      - "8180:8080"
    command: start-dev
    depends_on:
      keycloak-postgres:
        condition: service_healthy

volumes:
  keycloak-pgdata:

⚠️ Atenção: O comando start-dev habilita o modo de desenvolvimento com HTTP, cache local e hot-reload de temas. Nunca use start-dev em produção — use start com HTTPS configurado e as variáveis de produção adequadas.

Inicie o stack:

1
docker compose -f docker-compose.keycloak.yml up -d

Após alguns segundos, acesse o painel admin em http://localhost:8180 com as credenciais admin / admin.

Configuração Inicial: Realm e Client

O Keycloak organiza tudo em Realms — cada realm é um espaço isolado com seus próprios usuários, clients, roles e configurações. O realm master é reservado para administração do servidor. Vamos criar um realm para sua aplicação:

1. Criar um Realm

  1. No painel admin, clique no dropdown master no canto superior esquerdo
  2. Clique em Create Realm
  3. Preencha: Realm name = meu-app
  4. Clique em Create

2. Criar um Client (para a API ASP.NET Core)

  1. No realm meu-app, vá em ClientsCreate client
  2. Preencha:
    • Client type: OpenID Connect
    • Client ID: minha-api
  3. Na tela seguinte:
    • Client authentication: ON (confidential client)
    • Authorization: ON (se quiser usar authorization services)
  4. Em Valid Redirect URIs: https://localhost:5001/* e http://localhost:5000/*
  5. Em Web Origins: + (permite todos os origins dos redirect URIs)
  6. Clique em Save

3. Obter o Client Secret

  1. Após salvar, vá na aba Credentials
  2. Copie o Client secret — você vai precisar dele na configuração da API

4. Criar um Usuário de Teste

  1. Vá em UsersAdd user
  2. Preencha:
    • Username: usuario.teste
    • Email: [email protected]
    • First Name: Usuário
    • Last Name: Teste
    • Email verified: ON
  3. Clique em Create
  4. Vá na aba CredentialsSet password
  5. Defina uma senha e desmarque Temporary

5. Criar Roles

  1. Vá em Realm rolesCreate role
  2. Crie as roles: admin, user, manager
  3. Associe roles ao usuário: Users → selecione o usuário → aba Role mappingAssign role

ℹ️ Informação: Tudo que fizemos manualmente acima pode ser automatizado via Keycloak Admin REST API ou via importação de realm em JSON — ideal para pipelines de CI/CD.

Visão Geral do Fluxo de Autenticação

Antes de mergulhar no código, veja como as peças se encaixam. O diagrama abaixo mostra o fluxo completo: o Browser autentica via OpenID Connect no Keycloak, recebe um JWT e o envia como Bearer Token para a API ASP.NET Core, que valida o token consultando as chaves públicas (JWKS) do Keycloak:

Diagrama do fluxo de autenticação entre Browser, Keycloak e API ASP.NET Core em containers Docker com PostgreSQL

Integração com C# e ASP.NET Core

Os exemplos a seguir usam C# com ASP.NET Core, mas o mesmo fluxo se aplica a qualquer linguagem/framework. O Keycloak se comunica via protocolos padrão (OIDC/OAuth2), então a integração se resume a configurar a validação de JWT Bearer na sua stack — algo que todas as plataformas modernas suportam nativamente ou via biblioteca:

Linguagem / FrameworkBiblioteca / Recurso de Integração
C# / ASP.NET CoreMicrosoft.AspNetCore.Authentication.JwtBearer (usado neste artigo)
Java / Spring BootSpring Security OAuth2 Resource Serverspring-boot-starter-oauth2-resource-server
Python / Djangodjango-allauth ou mozilla-django-oidc
Python / FastAPIpython-jose + validação manual de JWT ou fastapi-keycloak
Node.js / Expresskeycloak-connect (adaptador oficial) ou jsonwebtoken + JWKS
Node.js / NestJSnest-keycloak-connect
Gogo-oidc (CoreOS)
PHP / Laravelsocialiteproviders/keycloak ou robsontenorio/laravel-keycloak-guard
Ruby on Railsomniauth-keycloak
Angular / React / Vuekeycloak-js (adaptador oficial para SPAs)

ℹ️ Informação: O Keycloak também oferece adaptadores oficiais para várias plataformas. No entanto, para novas integrações, a recomendação é usar bibliotecas genéricas de OIDC/JWT da sua linguagem — assim você não fica acoplado a um adaptador específico do Keycloak.

Configuração da API

Vamos criar uma API REST protegida pelo Keycloak usando ASP.NET Core 8 com autenticação OpenID Connect / JWT Bearer:

1
2
3
dotnet new webapi -n MinhaApi.Keycloak -f net8.0
cd MinhaApi.Keycloak
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Configure o appsettings.json com os dados do Keycloak:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "Keycloak": {
    "Authority": "http://localhost:8180/realms/meu-app",
    "Audience": "minha-api",
    "ClientId": "minha-api",
    "ClientSecret": "SEU_CLIENT_SECRET_AQUI",
    "RequireHttpsMetadata": false
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  }
}

⚠️ Atenção: Em produção, RequireHttpsMetadata deve ser true e o ClientSecret deve vir de variáveis de ambiente ou Azure Key Vault, nunca hardcoded no appsettings.json.

Program.cs — Configuração Completa

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

// Carrega configuração do Keycloak
var keycloakConfig = builder.Configuration.GetSection("Keycloak");

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.Authority = keycloakConfig["Authority"];
    options.Audience = keycloakConfig["Audience"];
    options.RequireHttpsMetadata = bool.Parse(
        keycloakConfig["RequireHttpsMetadata"] ?? "true"
    );

    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidIssuer = keycloakConfig["Authority"],
        ValidateAudience = true,
        ValidAudience = keycloakConfig["Audience"],
        ValidateLifetime = true,
        // Mapeia as roles do Keycloak para claims do .NET
        RoleClaimType = ClaimTypes.Role,
        NameClaimType = "preferred_username"
    };

    // Extrai as realm roles do token JWT do Keycloak
    options.Events = new JwtBearerEvents
    {
        OnTokenValidated = context =>
        {
            MapKeycloakRolesToClaims(context);
            return Task.CompletedTask;
        }
    };
});

builder.Services.AddAuthorization(options =>
{
    // Políticas baseadas em roles do Keycloak
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("admin"));

    options.AddPolicy("UserOrAdmin", policy =>
        policy.RequireRole("user", "admin"));

    options.AddPolicy("ManagerOnly", policy =>
        policy.RequireRole("manager"));
});

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

// Extrai realm_access.roles do token JWT do Keycloak
// e adiciona como claims de Role no ClaimsPrincipal
static void MapKeycloakRolesToClaims(TokenValidatedContext context)
{
    if (context.Principal?.Identity is not ClaimsIdentity identity)
        return;

    // Keycloak armazena roles em realm_access.roles no token JWT
    var realmAccess = context.Principal.FindFirst("realm_access");
    if (realmAccess is null)
        return;

    using var doc = System.Text.Json.JsonDocument.Parse(realmAccess.Value);

    if (!doc.RootElement.TryGetProperty("roles", out var roles))
        return;

    foreach (var role in roles.EnumerateArray())
    {
        var roleName = role.GetString();
        if (!string.IsNullOrEmpty(roleName))
        {
            identity.AddClaim(new Claim(ClaimTypes.Role, roleName));
        }
    }
}

💡 Dica: O Keycloak armazena as roles do realm dentro de um claim chamado realm_access no JWT. O código acima extrai essas roles e as mapeia para ClaimTypes.Role do .NET, permitindo usar [Authorize(Roles = "admin")] normalmente. Sem esse mapeamento, o User.IsInRole("admin") não funciona.

Controller Protegido

Crie um controller que demonstra os diferentes níveis de acesso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace MinhaApi.Keycloak.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProtegidoController : ControllerBase
{
    // Rota pública — sem autenticação
    [HttpGet("publico")]
    [AllowAnonymous]
    public IActionResult RotaPublica()
    {
        return Ok(new { mensagem = "Esta rota é pública e não requer autenticação." });
    }

    // Rota protegida — qualquer usuário autenticado
    [HttpGet("autenticado")]
    [Authorize]
    public IActionResult RotaAutenticada()
    {
        var username = User.FindFirst("preferred_username")?.Value;
        var email = User.FindFirst("email")?.Value;
        var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value);

        return Ok(new
        {
            mensagem = "Você está autenticado via Keycloak!",
            usuario = username,
            email,
            roles
        });
    }

    // Rota restrita a admins
    [HttpGet("admin")]
    [Authorize(Policy = "AdminOnly")]
    public IActionResult RotaAdmin()
    {
        return Ok(new
        {
            mensagem = "Acesso administrativo concedido.",
            usuario = User.FindFirst("preferred_username")?.Value
        });
    }

    // Rota restrita a managers
    [HttpGet("manager")]
    [Authorize(Policy = "ManagerOnly")]
    public IActionResult RotaManager()
    {
        return Ok(new
        {
            mensagem = "Acesso de manager concedido.",
            usuario = User.FindFirst("preferred_username")?.Value
        });
    }

    // Retorna todas as claims do token JWT
    [HttpGet("claims")]
    [Authorize]
    public IActionResult VerClaims()
    {
        var claims = User.Claims.Select(c => new
        {
            tipo = c.Type,
            valor = c.Value
        });

        return Ok(claims);
    }
}

Obtendo um Token para Testes

Para testar a API, você precisa obter um token do Keycloak. Use o fluxo Resource Owner Password (apenas para testes/desenvolvimento):

1
2
3
4
5
6
7
8
curl -X POST "http://localhost:8180/realms/meu-app/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=minha-api" \
  -d "client_secret=SEU_CLIENT_SECRET" \
  -d "grant_type=password" \
  -d "username=usuario.teste" \
  -d "password=sua-senha" \
  -d "scope=openid"

A resposta conterá o access_token. Use-o para chamar a API:

1
2
curl -H "Authorization: Bearer SEU_ACCESS_TOKEN" \
  http://localhost:5000/api/protegido/autenticado

Output esperado:

1
2
3
4
5
6
{
  "mensagem": "Você está autenticado via Keycloak!",
  "usuario": "usuario.teste",
  "email": "[email protected]",
  "roles": ["user"]
}

Service para Comunicação com a Admin API

Para cenários onde sua aplicação precisa gerenciar usuários, roles ou sessões programaticamente, o Keycloak expõe uma Admin REST API completa. Veja como criar um serviço para consumir essa API:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace MinhaApi.Keycloak.Services;

public sealed class KeycloakAdminService
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    private readonly string _realm;
    private readonly string _clientId;
    private readonly string _clientSecret;

    public KeycloakAdminService(
        HttpClient httpClient,
        IConfiguration configuration)
    {
        _httpClient = httpClient;
        _baseUrl = configuration["Keycloak:Authority"]!
            .Replace($"/realms/{configuration["Keycloak:Realm"]}", "");
        _realm = configuration["Keycloak:Realm"] ?? "meu-app";
        _clientId = configuration["Keycloak:ClientId"]!;
        _clientSecret = configuration["Keycloak:ClientSecret"]!;
    }

    // Obtém um token de serviço (client credentials) para acessar a Admin API
    private async Task<string> GetAdminTokenAsync(
        CancellationToken cancellationToken = default)
    {
        var content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "client_credentials",
            ["client_id"] = _clientId,
            ["client_secret"] = _clientSecret
        });

        var response = await _httpClient.PostAsync(
            $"{_baseUrl}/realms/{_realm}/protocol/openid-connect/token",
            content,
            cancellationToken);

        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        using var doc = JsonDocument.Parse(json);
        return doc.RootElement.GetProperty("access_token").GetString()!;
    }

    // Lista todos os usuários do realm
    public async Task<JsonElement> ListarUsuariosAsync(
        int first = 0,
        int max = 100,
        CancellationToken cancellationToken = default)
    {
        var token = await GetAdminTokenAsync(cancellationToken);

        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", token);

        var response = await _httpClient.GetAsync(
            $"{_baseUrl}/admin/realms/{_realm}/users?first={first}&max={max}",
            cancellationToken);

        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync(cancellationToken);
        return JsonDocument.Parse(json).RootElement;
    }

    // Cria um novo usuário no Keycloak
    public async Task<bool> CriarUsuarioAsync(
        string username,
        string email,
        string firstName,
        string lastName,
        string senha,
        CancellationToken cancellationToken = default)
    {
        var token = await GetAdminTokenAsync(cancellationToken);

        _httpClient.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", token);

        var novoUsuario = new
        {
            username,
            email,
            firstName,
            lastName,
            enabled = true,
            emailVerified = true,
            credentials = new[]
            {
                new
                {
                    type = "password",
                    value = senha,
                    temporary = false
                }
            }
        };

        var content = new StringContent(
            JsonSerializer.Serialize(novoUsuario),
            Encoding.UTF8,
            "application/json");

        var response = await _httpClient.PostAsync(
            $"{_baseUrl}/admin/realms/{_realm}/users",
            content,
            cancellationToken);

        return response.IsSuccessStatusCode;
    }
}

Registre no Program.cs:

1
builder.Services.AddHttpClient<KeycloakAdminService>();

ℹ️ Informação: Para que o client minha-api consiga acessar a Admin API, é necessário habilitar a Service Account do client no Keycloak e associar a role realm-managementrealm-admin à service account.

Implementando o Logout

O logout em uma integração com o Keycloak vai além de simplesmente descartar o token no lado do cliente. Para que a sessão seja completamente encerrada, é necessário notificar o Keycloak para que ele invalide a sessão no servidor de identidade também — isso é chamado de RP-Initiated Logout (especificação OpenID Connect).

O Keycloak expõe o endpoint de logout em:

1
{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/logout

Logout via API (Back-Channel)

Para invalidar a sessão do usuário diretamente pela sua API, use o refresh_token ou o id_token_hint:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace MinhaApi.Keycloak.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _configuration;
    private readonly IHttpClientFactory _httpClientFactory;

    public AuthController(
        IConfiguration configuration,
        IHttpClientFactory httpClientFactory)
    {
        _configuration = configuration;
        _httpClientFactory = httpClientFactory;
    }

    /// <summary>
    /// Realiza o logout no Keycloak invalidando a sessão via back-channel.
    /// O client envia o refresh_token para que o Keycloak encerre a sessão.
    /// </summary>
    [HttpPost("logout")]
    [Authorize]
    public async Task<IActionResult> Logout(
        [FromBody] LogoutRequest request,
        CancellationToken cancellationToken = default)
    {
        var authority = _configuration["Keycloak:Authority"];
        var clientId = _configuration["Keycloak:ClientId"];
        var clientSecret = _configuration["Keycloak:ClientSecret"];

        var httpClient = _httpClientFactory.CreateClient();

        // Chama o endpoint de logout do Keycloak
        var content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["client_id"] = clientId!,
            ["client_secret"] = clientSecret!,
            ["refresh_token"] = request.RefreshToken
        });

        var response = await httpClient.PostAsync(
            $"{authority}/protocol/openid-connect/logout",
            content,
            cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            var error = await response.Content.ReadAsStringAsync(cancellationToken);
            return BadRequest(new
            {
                mensagem = "Falha ao realizar logout no Keycloak.",
                detalhes = error
            });
        }

        return Ok(new { mensagem = "Logout realizado com sucesso." });
    }

    /// <summary>
    /// Gera a URL de logout do Keycloak para redirecionamento (Front-Channel).
    /// Usado quando o client precisa redirecionar o usuário para a tela de logout do Keycloak.
    /// </summary>
    [HttpGet("logout-url")]
    [Authorize]
    public IActionResult GetLogoutUrl(
        [FromQuery] string? redirectUri = null)
    {
        var authority = _configuration["Keycloak:Authority"];
        var clientId = _configuration["Keycloak:ClientId"];

        var logoutUrl = $"{authority}/protocol/openid-connect/logout" +
                        $"?client_id={clientId}";

        if (!string.IsNullOrEmpty(redirectUri))
        {
            logoutUrl += $"&post_logout_redirect_uri={Uri.EscapeDataString(redirectUri)}";
        }

        return Ok(new { logoutUrl });
    }
}

public record LogoutRequest(string RefreshToken);

Registre o IHttpClientFactory no Program.cs (caso ainda não esteja registrado):

1
builder.Services.AddHttpClient();

Testando o Logout

1. Obtenha um token (incluindo o refresh_token):

1
2
3
4
5
6
7
8
curl -X POST "http://localhost:8180/realms/meu-app/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=minha-api" \
  -d "client_secret=SEU_CLIENT_SECRET" \
  -d "grant_type=password" \
  -d "username=usuario.teste" \
  -d "password=sua-senha" \
  -d "scope=openid offline_access"

💡 Dica: O scope offline_access garante que o Keycloak retorne um refresh_token na resposta. Sem ele, dependendo da configuração do client, o refresh token pode não ser emitido.

2. Realize o logout via API:

1
2
3
4
curl -X POST "http://localhost:5000/api/auth/logout" \
  -H "Authorization: Bearer SEU_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "SEU_REFRESH_TOKEN"}'

3. Ou obtenha a URL de logout para redirecionamento (front-channel):

1
2
curl -H "Authorization: Bearer SEU_ACCESS_TOKEN" \
  "http://localhost:5000/api/auth/logout-url?redirectUri=http://localhost:3000"

Logout no Front-End (SPA)

Para aplicações front-end (Angular, React, Vue), o logout geralmente é feito via redirecionamento para o endpoint do Keycloak:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Exemplo genérico para qualquer framework JavaScript
function logout() {
    const keycloakUrl = "http://localhost:8180";
    const realm = "meu-app";
    const clientId = "minha-api";
    const redirectUri = encodeURIComponent(window.location.origin);

    // Limpa tokens locais
    localStorage.removeItem("access_token");
    localStorage.removeItem("refresh_token");

    // Redireciona para o logout do Keycloak
    window.location.href =
        `${keycloakUrl}/realms/${realm}/protocol/openid-connect/logout` +
        `?client_id=${clientId}` +
        `&post_logout_redirect_uri=${redirectUri}`;
}

Se estiver usando o adaptador oficial keycloak-js, o logout é ainda mais simples:

1
2
3
4
5
6
7
8
9
// Com keycloak-js
const keycloak = new Keycloak({
    url: "http://localhost:8180",
    realm: "meu-app",
    clientId: "minha-api"
});

// Logout com redirecionamento
keycloak.logout({ redirectUri: window.location.origin });

⚠️ Atenção: Para que o post_logout_redirect_uri funcione, a URL de redirecionamento precisa estar cadastrada nas Valid Post Logout Redirect URIs do client no Keycloak. Em desenvolvimento, adicione http://localhost:*. Em produção, use apenas as URLs exatas do seu domínio.

Customização da Tela de Login

Uma das grandes vantagens do Keycloak sobre provedores de cloud é a liberdade total para customizar a tela de login. Você pode trocar o logo, alterar cores, fontes, textos e até criar temas completamente do zero usando templates FreeMarker.

Estrutura de um Tema Keycloak

O Keycloak usa um sistema de temas baseado em FreeMarker Templates. Cada tema pode sobrescrever parcialmente o tema padrão (keycloak ou keycloak.v2):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
themes/
└── meu-tema/
    └── login/
        ├── theme.properties          # Configurações do tema
        ├── resources/
        │   ├── css/
        │   │   └── login.css         # Estilos customizados
        │   └── img/
        │       ├── logo.svg          # Logo customizado
        │       └── favicon.ico       # Favicon
        └── messages/
            └── messages_pt_BR.properties  # Textos em PT-BR

Criando o Tema Customizado

1. Arquivo theme.properties

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Herda do tema padrão do Keycloak e sobrescreve apenas o necessário
parent=keycloak.v2
import=common/keycloak

# Estilos customizados
styles=css/login.css

# Sobrescreve o logo
logo=img/logo.svg
logoUrl=https://minha-empresa.com.br

2. CSS Customizado (resources/css/login.css)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* ===== Cores e Branding ===== */
:root {
    --pf-v5-global--primary-color--100: #1a73e8;    /* Azul primário */
    --pf-v5-global--primary-color--200: #1557b0;    /* Azul hover */
    --pf-v5-global--BackgroundColor--100: #f8f9fa;   /* Fundo da página */
}

/* ===== Logo personalizado ===== */
.pf-v5-c-login__header .pf-v5-c-brand {
    max-height: 80px;
    width: auto;
}

/* ===== Card de login ===== */
.pf-v5-c-login__main {
    background-color: #ffffff;
    border-radius: 12px;
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12);
    max-width: 440px;
}

/* ===== Botão de login ===== */
.pf-v5-c-button.pf-m-primary {
    background-color: var(--pf-v5-global--primary-color--100);
    border-radius: 8px;
    font-weight: 600;
    padding: 12px 24px;
    transition: background-color 0.2s ease;
}

.pf-v5-c-button.pf-m-primary:hover {
    background-color: var(--pf-v5-global--primary-color--200);
}

/* ===== Campos de input ===== */
.pf-v5-c-form-control {
    border-radius: 8px;
    border: 1px solid #dadce0;
    padding: 12px 16px;
}

.pf-v5-c-form-control:focus {
    border-color: var(--pf-v5-global--primary-color--100);
    box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
}

/* ===== Footer customizado ===== */
.pf-v5-c-login__footer {
    text-align: center;
    font-size: 0.85rem;
    color: #5f6368;
}

/* ===== Background com gradiente ===== */
.pf-v5-c-login {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
}

3. Mensagens em Português (messages/messages_pt_BR.properties)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Sobrescreve textos padrão para PT-BR
loginTitle=Bem-vindo de volta
loginTitleHtml=Bem-vindo de volta
doLogIn=Entrar
doRegister=Criar conta
noAccount=Não tem uma conta?
usernameOrEmail=E-mail ou nome de usuário
password=Senha
rememberMe=Lembrar de mim
forgotPassword=Esqueceu a senha?
backToLogin=Voltar ao login

Montando o Tema no Container

Adicione o volume do tema no docker-compose.keycloak.yml:

1
2
3
4
5
6
7
8
  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    # ... demais configurações ...
    volumes:
      - ./themes/meu-tema:/opt/keycloak/themes/meu-tema:ro
    environment:
      # ... demais variáveis ...
      KC_SPI_THEME_DEFAULT: meu-tema

Alternativamente, ative o tema pelo painel admin:

  1. Vá em Realm Settings → aba Themes
  2. Em Login theme, selecione meu-tema
  3. Clique em Save

💡 Dica: No modo start-dev, o Keycloak faz hot-reload dos temas automaticamente. Basta salvar o arquivo CSS ou o template FreeMarker e recarregar a página de login para ver as mudanças instantaneamente — sem precisar reiniciar o container.

Resultado Visual

Após aplicar o tema, a tela de login terá:

  • ✅ Logo da sua empresa no topo
  • ✅ Cores personalizadas nos botões e campos
  • ✅ Textos em português brasileiro
  • ✅ Card de login com bordas arredondadas e sombra suave
  • ✅ Background com gradiente customizado
  • ✅ Favicon personalizado

📝 Exemplo: Empresas como a Red Hat, Siemens e Deutsche Telekom usam Keycloak com temas totalmente customizados que são indistinguíveis de soluções SaaS proprietárias. O usuário final nunca percebe que está usando um identity provider open-source.

Keycloak em Produção com Container

Docker Compose para Produção

Para produção, as diferenças em relação ao setup de desenvolvimento são:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# docker-compose.keycloak.prod.yml
services:
  keycloak-postgres:
    image: postgres:16-alpine
    container_name: keycloak-postgres
    restart: always
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
      TZ: America/Sao_Paulo
    volumes:
      - keycloak-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    container_name: keycloak
    restart: always
    environment:
      # Admin — usar senha segura gerada com openssl rand
      KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_ADMIN_USER}
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
      # Banco
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      # Produção — HTTPS obrigatório
      KC_HOSTNAME: auth.seudominio.com.br
      KC_PROXY_HEADERS: xforwarded
      KC_HTTP_ENABLED: "false"
      KC_HTTPS_CERTIFICATE_FILE: /opt/keycloak/certs/fullchain.pem
      KC_HTTPS_CERTIFICATE_KEY_FILE: /opt/keycloak/certs/privkey.pem
      # Performance
      KC_CACHE: ispn
      KC_HEALTH_ENABLED: "true"
      KC_METRICS_ENABLED: "true"
      TZ: America/Sao_Paulo
    ports:
      - "8443:8443"
    command: start --optimized
    volumes:
      - ./certs:/opt/keycloak/certs:ro
      - ./themes/meu-tema:/opt/keycloak/themes/meu-tema:ro
    depends_on:
      keycloak-postgres:
        condition: service_healthy

volumes:
  keycloak-pgdata:

⚠️ Atenção: Para produção, considere usar build ao invés de volumes para o tema. A imagem oficial suporta multi-stage build com kc.sh build para otimizar o startup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FROM quay.io/keycloak/keycloak:26.0 AS builder

# Copia o tema customizado
COPY themes/meu-tema /opt/keycloak/themes/meu-tema

# Build otimizado com as configurações de produção
ENV KC_DB=postgres
ENV KC_CACHE=ispn
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true

RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:26.0

COPY --from=builder /opt/keycloak/ /opt/keycloak/

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

Dicas de Produção — Segurança e Performance

Proxy reverso com Nginx:

Se o Keycloak estiver atrás de um Nginx (como na arquitetura descrita no padrão BFF), configure o location para proxy reverso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
server {
    listen 443 ssl http2;
    server_name auth.seudominio.com.br;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    location / {
        proxy_pass https://keycloak:8443;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

Dicas e Boas Práticas

  • Separe o realm de produção do admin — nunca use o realm master para seus usuários. Crie realms específicos por aplicação ou domínio de negócio.

  • Exporte e versione a configuração do realm — use o endpoint de exportação do Keycloak (/admin/realms/{realm}/export) para salvar a configuração como JSON e versioná-la no Git. Isso permite recriar o ambiente inteiro a partir do código (Infrastructure as Code).

  • Habilite rate limiting no proxy reverso — o Keycloak lida com autenticação, que é alvo frequente de ataques de força bruta. Configure limit_req no Nginx para restringir requisições ao endpoint de login.

  • Configure o cache do Infinispan corretamente — em produção com múltiplas instâncias (cluster), o Keycloak usa o Infinispan para cache distribuído de sessões. Consulte a documentação oficial de caching para cenários de alta disponibilidade.

  • Monitore com health checks e métricas — habilite KC_HEALTH_ENABLED e KC_METRICS_ENABLED para expor endpoints de /health e /metrics (compatíveis com Prometheus).

  • Use start --optimized em produção — o comando start com a flag --optimized pré-compila as configurações e reduz drasticamente o tempo de startup (de ~30s para ~5s).

  • Atualize regularmente — o Keycloak recebe atualizações de segurança frequentes. Siga o blog oficial e teste novas versões em staging antes de atualizar produção.

  • Faça backup do banco regularmente — o PostgreSQL do Keycloak contém todos os dados de identidade. Um pg_dump agendado é obrigatório para qualquer ambiente de produção.

Comparativo: Quando Usar Keycloak vs Cloud

CenárioRecomendação
Startup com orçamento limitadoKeycloak — zero custo, todas as features
Empresa já no ecossistema Microsoft 365Azure Entra ID — integração nativa
Aplicação 100% AWS com ServerlessAWS Cognito — integração com Lambda/API Gateway
Requisito de soberania de dados (LGPD, GDPR)Keycloak — dados ficam na sua infra
Time sem experiência em infra/containersAuth0 ou Firebase Auth — SaaS gerenciado
Cenário on-premises ou air-gappedKeycloak — única opção viável
Necessidade de customização total da UXKeycloak — temas FreeMarker ilimitados
Alta escala (milhões de MAU)🤔 Depende — Keycloak escala, mas exige expertise em cluster

ℹ️ Informação: Nada impede o uso híbrido. Muitas empresas usam o Keycloak como identity broker federando com Azure AD/Google Workspace. Assim, os funcionários fazem login com SSO corporativo (Azure AD), mas os dados de sessão e autorização ficam centralizados no Keycloak.

Conclusão

O Keycloak é uma das ferramentas mais subestimadas do ecossistema open-source. Ele entrega, gratuitamente, funcionalidades que provedores de cloud cobram centenas ou milhares de dólares por mês: SSO, MFA, identity brokering, user federation, customização completa de UI e uma API REST poderosa para automação. Rodar em container com Docker ou Podman é trivial, e a integração via OpenID Connect é direta — neste artigo demonstramos com ASP.NET Core e C#, mas o mesmo fluxo funciona com Java, Python, Node.js, Go, PHP, Ruby e qualquer outra tecnologia que suporte JWT e OIDC. O Keycloak é um servidor de identidade agnóstico de linguagem: a camada de autenticação roda no container, e sua aplicação apenas valida tokens padrão.

Se você trabalha em um cenário onde o custo importa, onde a soberania de dados é requisito, ou simplesmente quer controle total sobre a autenticação e autorização da sua aplicação — independentemente da linguagem ou framework —, o Keycloak merece um lugar de destaque na sua stack. Comece pequeno, em ambiente de desenvolvimento com Docker Compose, valide o fluxo com sua aplicação, customize a tela de login com a identidade visual da sua marca e, quando estiver confiante, promova para produção com HTTPS, proxy reverso e backups.

A autenticação é a porta de entrada do seu sistema — e com o Keycloak, essa porta é gratuita, segura e completamente sua.

Leia Também

Referências