BTW.KeycloakJwtGenerator 1.1.0

dotnet add package BTW.KeycloakJwtGenerator --version 1.1.0
                    
NuGet\Install-Package BTW.KeycloakJwtGenerator -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BTW.KeycloakJwtGenerator" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BTW.KeycloakJwtGenerator" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="BTW.KeycloakJwtGenerator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BTW.KeycloakJwtGenerator --version 1.1.0
                    
#r "nuget: BTW.KeycloakJwtGenerator, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BTW.KeycloakJwtGenerator@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BTW.KeycloakJwtGenerator&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=BTW.KeycloakJwtGenerator&version=1.1.0
                    
Install as a Cake Tool

📦 BTW.KeycloakJwtGenerator

NuGet License .NET

Librería .NET para generar JWT centralizados usando Keycloak como Identity Provider. Permite que sistemas con autenticación propia (LDAP, Base de Datos, Active Directory, etc.) generen tokens JWT estandarizados con claims personalizados.

🎯 ¿Para Qué Sirve?

Tu Sistema (auth propia) → BTW.KeycloakJwtGenerator → Keycloak → JWT con tus claims
  • ✅ Tu sistema mantiene su mecanismo de autenticación (BD, LDAP, AD)
  • ✅ Keycloak genera JWT estandarizados
  • ✅ Incluye los claims personalizados que necesites
  • ✅ Todos tus sistemas validan el mismo JWT

📦 Instalación

dotnet add package BTW.KeycloakJwtGenerator

⚡ Requisitos

Compatible con .NET 6.0, .NET 7.0, .NET 8.0 y .NET 10.0.

¿Otros targets?

🚀 Uso Rápido

1. Configurar en appsettings.json

{
  "KeycloakJwt": {
    "BaseUrl": "http://localhost:8080",
    "Realm": "mi-realm",
    "ClientId": "mi-sistema",
    "ClientSecret": "mi-secret",
    "SystemName": "Sistema A"
  }
}

2. Registrar en Program.cs

// Opción 1: Desde appsettings.json
builder.Services.AddKeycloakJwtGenerator(builder.Configuration);

// Opción 2: Configuración fluida
builder.Services
    .AddKeycloakJwt()
    .WithBaseUrl("http://localhost:8080")
    .WithRealm("mi-realm")
    .WithClient("mi-cliente", "mi-secret")
    .WithSystemName("Sistema A")
    .Build();

// Opción 3: Con Action
builder.Services.AddKeycloakJwtGenerator(options =>
{
    options.BaseUrl = "http://localhost:8080";
    options.Realm = "mi-realm";
    options.ClientId = "mi-cliente";
    options.ClientSecret = "mi-secret";
    options.SystemName = "Sistema A";
});

3. Usar en tu código

public class AuthController : ControllerBase
{
    private readonly IKeycloakJwtGenerator _jwtGenerator;

    public AuthController(IKeycloakJwtGenerator jwtGenerator)
    {
        _jwtGenerator = jwtGenerator;
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login(LoginRequest request)
    {
        // 1. Tu autenticación local (BD, LDAP, etc.)
        var user = await _myAuthService.ValidateAsync(request.Username, request.Password);
        
        if (user == null)
            return Unauthorized();

        // 2. Definir claims personalizados
        var claims = new Dictionary<string, object>
        {
            ["departamento"] = user.Departamento,
            ["nivel_acceso"] = user.NivelAcceso,
            ["permisos"] = user.Permisos,        // Array
            ["sucursal"] = user.Sucursal,
            ["metadata"] = new {                  // Objeto anidado
                zona = user.Zona,
                codigo = user.Codigo
            }
        };

        // 3. Generar JWT con Keycloak
        var token = await _jwtGenerator.GenerateTokenAsync(request.Username, claims);

        return Ok(new
        {
            accessToken = token.AccessToken,
            refreshToken = token.RefreshToken,
            expiresIn = token.ExpiresIn
        });
    }
}

📋 API Completa

Generar Token (Simple)

var token = await _jwtGenerator.GenerateTokenAsync(
    "usuario",
    new Dictionary<string, object>
    {
        ["departamento"] = "ventas",
        ["permisos"] = new[] { "leer", "escribir" }
    });

Generar Token (Request Completo)

var token = await _jwtGenerator.GenerateTokenAsync(new GenerateTokenRequest
{
    Username = "usuario",
    Email = "usuario@empresa.com",
    FirstName = "Juan",
    LastName = "Pérez",
    Roles = new List<string> { "user", "admin" },
    Scope = "admin:dictionary",   // scopes OAuth per-request (opcional)
    Claims = new Dictionary<string, object>
    {
        ["departamento"] = "ventas"
    }
});

Solicitar Scopes per-request (scope)

El campo Scope (string separado por espacios) se envía como parámetro scope en el grant de Keycloak. Sirve para incluir Client Scopes de tipo Optional en el claim scope del token solo cuando se piden, sin dejar atributos persistidos en el usuario.

// Con scope → el access token incluye "admin:dictionary" en el claim "scope"
await _jwtGenerator.GenerateTokenAsync(new GenerateTokenRequest
{
    Username = "usuario",
    Scope = "admin:dictionary inventory:read"
});

// Sin Scope → el token no lleva esos scopes
await _jwtGenerator.GenerateTokenAsync(new GenerateTokenRequest { Username = "usuario" });

Requiere que el scope esté dado de alta como Optional Client Scope del client (ver Configuración de Keycloak) y Token Exchange habilitado.

Refrescar Token

var newToken = await _jwtGenerator.RefreshTokenAsync(refreshToken);

Validar Token

bool isValid = await _jwtGenerator.ValidateTokenAsync(accessToken);

Revocar Token (Logout)

await _jwtGenerator.RevokeTokenAsync(refreshToken);

🔗 gRPC — Propagación del JWT entre Microservicios

Para arquitecturas donde Microservicio A llama a Microservicio B vía gRPC usando el mismo JWT del usuario:

1. Registrar el handler

// Program.cs / Startup.cs
builder.Services.AddKeycloakJwtValidation(builder.Configuration); // valida JWTs entrantes
builder.Services.AddKeycloakGrpcForwarding();                     // propaga JWT a llamadas salientes

2. Adjuntarlo al cliente gRPC

builder.Services
    .AddGrpcClient<OrderService.OrderServiceClient>(o =>
        o.Address = new Uri("https://orders-service"))
    .AddHttpMessageHandler<JwtForwardingHandler>();

3. Proteger el servicio receptor con [Authorize]

// En el Microservicio B (servicio gRPC receptor)
builder.Services.AddKeycloakJwtValidation(builder.Configuration);

// En el servicio gRPC
public class OrderGrpcService : OrderService.OrderServiceBase
{
    [Authorize]
    public override Task<OrderReply> GetOrder(OrderRequest request, ServerCallContext context)
    {
        // Claims del JWT entrante disponibles vía User
        var departamento = context.GetHttpContext().User.FindFirst("departamento")?.Value;
        return Task.FromResult(new OrderReply { ... });
    }
}

Cómo funciona: JwtForwardingHandler lee la cabecera Authorization de la petición HTTP entrante (vía IHttpContextAccessor) y la añade automáticamente a todas las llamadas gRPC salientes del servicio. El JWT lo firma Keycloak, por lo que es válido en cualquier servicio que apunte al mismo realm.

Rol del servicio Método
Emisor (genera JWT) AddKeycloakJwtGenerator()
Intermediario (valida + reenvía) AddKeycloakJwtValidation() + AddKeycloakGrpcForwarding()
Receptor (solo valida) AddKeycloakJwtValidation()

🔐 Validar JWT en Otros Sistemas

Para sistemas que solo necesitan validar tokens (no generarlos):

// Solo agrega validación de JWT
builder.Services.AddKeycloakJwtValidation(builder.Configuration);

// O ambos (generar y validar)
builder.Services.AddKeycloakJwtFull(builder.Configuration);

Luego usa [Authorize] normalmente:

[Authorize]
[HttpGet("protected")]
public IActionResult Protected()
{
    var departamento = User.FindFirst("departamento")?.Value;
    return Ok(new { departamento });
}

⚙️ Configuración Completa

{
  "KeycloakJwt": {
    "BaseUrl": "http://localhost:8080",
    "Realm": "mi-realm",
    "ClientId": "mi-sistema",
    "ClientSecret": "secret",
    "SystemName": "Sistema A",
    "TimeoutSeconds": 30,
    "EnableAdminTokenCache": true,
    "AdminTokenCacheMinutes": 4,
    "UseTokenExchange": true,
    "MaxRetries": 3
  }
}
Opción Descripción Default
BaseUrl URL de Keycloak http://localhost:8080
Realm Nombre del Realm master
ClientId Client ID del service account -
ClientSecret Client Secret -
SystemName Nombre del sistema (claim) null
TimeoutSeconds Timeout HTTP 30
EnableAdminTokenCache Cachear token admin true
AdminTokenCacheMinutes Tiempo de caché 4
UseTokenExchange Usar Token Exchange true
MaxRetries Reintentos en fallo 3

🛠️ Configuración de Keycloak

1. Crear Client (Service Account)

Clients → Create client
├── Client ID: "mi-sistema"
├── Client authentication: ON
├── Service accounts roles: ✅
└── Save

2. Asignar Roles al Service Account

Client → Service account roles → Assign role
├── manage-users
├── view-users
└── impersonation (para Token Exchange)

3. Crear Protocol Mappers

Client → Client scopes → Dedicated scope → Mappers
├── Mapper type: "User Attribute"
├── User Attribute: "departamento"
├── Token Claim Name: "departamento"
├── Add to access token: ✅
└── Save

4. Habilitar Token Exchange (opcional)

Si estás ejecutando Keycloak con Docker, agrega esta configuración para habilitar Token Exchange:

# docker-compose.yml (configuración de Keycloak, no de tu aplicación)
services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    environment:
      KC_FEATURES: token-exchange
      # ... otras configuraciones

Nota: Esta configuración es solo para Keycloak. Si Keycloak ya tiene Token Exchange habilitado o usas otra forma de instalación, puedes omitir este paso.

5. Client Scopes Opcionales (per-request)

Para usar el campo Scope del request (incluir un scope en el token solo cuando se pide):

Client scopes → Create client scope
├── Name: admin:dictionary
├── Type: Optional
├── Protocol: openid-connect
└── Save

Clients → tu client → Client scopes → Add client scope
├── admin:dictionary
└── Assigned type: Optional

Un scope Optional solo se incluye en el token cuando se envía en el parámetro scope (lo que hace GenerateTokenRequest.Scope). Si lo asignas como Default, irá en todos los tokens del client y Scope deja de tener efecto diferenciador.

📄 Licencia

MIT License - ver LICENSE


Desarrollado con ❤️ por By The Wave (BTW)

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 87 6/23/2026
1.0.1 128 4/28/2026
1.0.0 465 12/10/2025