BTW.KeycloakJwtGenerator.NetFramework461
1.0.1
dotnet add package BTW.KeycloakJwtGenerator.NetFramework461 --version 1.0.1
NuGet\Install-Package BTW.KeycloakJwtGenerator.NetFramework461 -Version 1.0.1
<PackageReference Include="BTW.KeycloakJwtGenerator.NetFramework461" Version="1.0.1" />
<PackageVersion Include="BTW.KeycloakJwtGenerator.NetFramework461" Version="1.0.1" />
<PackageReference Include="BTW.KeycloakJwtGenerator.NetFramework461" />
paket add BTW.KeycloakJwtGenerator.NetFramework461 --version 1.0.1
#r "nuget: BTW.KeycloakJwtGenerator.NetFramework461, 1.0.1"
#:package BTW.KeycloakJwtGenerator.NetFramework461@1.0.1
#addin nuget:?package=BTW.KeycloakJwtGenerator.NetFramework461&version=1.0.1
#tool nuget:?package=BTW.KeycloakJwtGenerator.NetFramework461&version=1.0.1
BTW.KeycloakJwtGenerator.NetFramework461
Variante .NET Framework 4.6.1 de BTW.KeycloakJwtGenerator para sistemas ASP.NET Web API 2 + OWIN. Genera JWTs centralizados usando Keycloak como Identity Provider y valida tokens vía OWIN JwtBearer middleware.
Para .NET 6 / 7 / 8 / 10 usa el paquete principal
BTW.KeycloakJwtGenerator.
Para .NET Core 3.1 usaBTW.KeycloakJwtGenerator.NetCore31.
¿Para qué sirve?
Tu Sistema Web API 2 (auth propia) → BTW.KeycloakJwtGenerator → Keycloak → JWT con tus claims
- ✅ Tu sistema mantiene su autenticación (BD, LDAP, Active Directory)
- ✅ Keycloak firma y emite el JWT
- ✅ Claims personalizados que tú defines
- ✅ Validación de JWT vía OWIN con JWKS cacheado (sin llamar a Keycloak por request)
- ✅ El JWT es válido en cualquier API que apunte al mismo realm (cross-stack)
Instalación
# Package Manager Console en Visual Studio
Install-Package BTW.KeycloakJwtGenerator.NetFramework461
# dotnet CLI (proyectos SDK-style)
dotnet add package BTW.KeycloakJwtGenerator.NetFramework461
Configuración en Web.config
<configuration>
<appSettings>
<add key="KeycloakJwt:BaseUrl" value="http://localhost:8080" />
<add key="KeycloakJwt:Realm" value="mi-realm" />
<add key="KeycloakJwt:ClientId" value="mi-sistema" />
<add key="KeycloakJwt:ClientSecret" value="mi-secret" />
<add key="KeycloakJwt:SystemName" value="Sistema A" />
<add key="KeycloakJwt:UseTokenExchange" value="true" />
<add key="KeycloakJwt:TimeoutSeconds" value="30" />
<add key="KeycloakJwt:AdminTokenCacheMinutes" value="4" />
</appSettings>
</configuration>
Registro en Startup.cs (OWIN)
using System.Web.Http;
using Owin;
using BTW.KeycloakJwtGenerator.Configuration;
using BTW.KeycloakJwtGenerator.Extensions;
[assembly: Microsoft.Owin.OwinStartup(typeof(MiApi.Startup))]
namespace MiApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 1. Leer opciones desde Web.config
var options = KeycloakJwtOptions.FromAppSettings();
// 2. Validación JWT vía OWIN (debe ir antes de UseWebApi)
app.UseKeycloakJwtValidation(options);
// 3. Web API 2
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
// 4. Registrar IKeycloakJwtGenerator en tu container DI
// (Autofac, Unity, SimpleInjector, etc.)
// o manualmente:
config.DependencyResolver = ConfigureDependencies(config, options);
app.UseWebApi(config);
}
}
}
UseKeycloakJwtValidationdescarga y cachea automáticamente el JWKS desde{BaseUrl}/realms/{Realm}/.well-known/openid-configuration. Las claves se actualizan cuando Keycloak rota su par de claves.
Uso — Controlador de login
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
using BTW.KeycloakJwtGenerator.Exceptions;
using BTW.KeycloakJwtGenerator.Services;
[RoutePrefix("api/auth")]
public class AuthController : ApiController
{
private readonly IMyAuthService _myAuth;
private readonly IKeycloakJwtGenerator _jwt;
public AuthController(IMyAuthService myAuth, IKeycloakJwtGenerator jwt)
{
_myAuth = myAuth;
_jwt = jwt;
}
[HttpPost, Route("login"), AllowAnonymous]
public async Task<IHttpActionResult> Login(LoginRequest req)
{
// 1. Tu autenticación propia (BD, LDAP, etc.)
var user = await _myAuth.ValidateAsync(req.Username, req.Password);
if (user == null) return Unauthorized();
// 2. Claims que decides poner en el JWT
var claims = new Dictionary<string, object>
{
["departamento"] = user.Departamento,
["sucursal"] = user.Sucursal,
["permisos"] = user.Permisos // string[]
};
// 3. Generar JWT via Keycloak
var token = await _jwt.GenerateTokenAsync(req.Username, claims);
return Ok(token);
}
[HttpPost, Route("refresh"), AllowAnonymous]
public async Task<IHttpActionResult> Refresh(RefreshRequest req)
{
try
{
var token = await _jwt.RefreshTokenAsync(req.RefreshToken);
return Ok(token);
}
catch (KeycloakAuthenticationException)
{
return Unauthorized();
}
}
[HttpPost, Route("logout"), Authorize]
public async Task<IHttpActionResult> Logout(LogoutRequest req)
{
await _jwt.RevokeTokenAsync(req.RefreshToken);
return StatusCode(System.Net.HttpStatusCode.NoContent);
}
}
Importante: usa
System.Web.Http.AuthorizeAttribute(Web API 2), noSystem.Web.Mvc.AuthorizeAttribute.
Uso — Controlador protegido
using System.Security.Claims;
using System.Web.Http;
[Authorize] // System.Web.Http.AuthorizeAttribute
[RoutePrefix("api/recurso")]
public class RecursoController : ApiController
{
[HttpGet, Route("")]
public IHttpActionResult Get()
{
var principal = User as ClaimsPrincipal;
var sub = principal?.FindFirst("sub")?.Value;
var departamento = principal?.FindFirst("departamento")?.Value;
var sucursal = principal?.FindFirst("sucursal")?.Value;
return Ok(new { sub, departamento, sucursal });
}
}
Registrar IKeycloakJwtGenerator en el container DI
Con Microsoft.Extensions.DependencyInjection (recomendado)
// En Startup.cs, dentro de Configuration():
var services = new ServiceCollection();
var options = KeycloakJwtOptions.FromAppSettings();
services.AddKeycloakJwtGenerator(opt =>
{
opt.BaseUrl = options.BaseUrl;
opt.Realm = options.Realm;
opt.ClientId = options.ClientId;
opt.ClientSecret = options.ClientSecret;
opt.SystemName = options.SystemName;
});
var provider = services.BuildServiceProvider();
config.DependencyResolver = new MicrosoftDependencyResolver(provider);
Con Autofac
var builder = new ContainerBuilder();
builder.Register(_ => options).As<KeycloakJwtOptions>().SingleInstance();
builder.RegisterType<KeycloakJwtGenerator>().As<IKeycloakJwtGenerator>();
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
API completa
| Método | Descripción |
|---|---|
GenerateTokenAsync(username, claims) |
Genera JWT: admin token → sync user → Token Exchange |
GenerateTokenAsync(GenerateTokenRequest) |
Igual, con email/nombre/roles opcionales |
RefreshTokenAsync(refreshToken) |
Renueva access + refresh token |
ValidateTokenAsync(token) |
Introspección remota en Keycloak |
RevokeTokenAsync(refreshToken) |
Logout en Keycloak |
KeycloakJwtOptions.FromAppSettings() |
Lee configuración desde Web.config / App.config |
app.UseKeycloakJwtValidation(options) |
Configura OWIN JwtBearer con JWKS cacheado |
Opciones de configuración
Opción (KeycloakJwt:*) |
Descripción | Default |
|---|---|---|
BaseUrl |
URL base de Keycloak | http://localhost:8080 |
Realm |
Nombre del Realm | master |
ClientId |
Client ID del service account | — |
ClientSecret |
Client Secret | — |
SystemName |
Nombre del sistema (claim sistema_origen) |
null |
TimeoutSeconds |
Timeout HTTP | 30 |
EnableAdminTokenCache |
Cachear el admin token | true |
AdminTokenCacheMinutes |
Tiempo de caché del admin token | 4 |
UseTokenExchange |
Usar Token Exchange (fallback a service account) | true |
MaxRetries |
Reintentos en fallo | 3 |
Interoperabilidad cross-stack
El JWT lo firma Keycloak, no la librería. Esto significa que un token emitido desde este paquete (net461) es válido en cualquier API que valide contra el mismo realm:
| Emisor | Validador | ¿Funciona? |
|---|---|---|
| net461 (este paquete) | net8 (BTW.KeycloakJwtGenerator) | ✅ |
| net461 | ASP.NET Core 3.1 | ✅ |
| net8 | net461 (este paquete) | ✅ |
| Java (Spring Security + Keycloak) | net461 | ✅ |
Requisito: BaseUrl y Realm deben ser idénticos entre emisor y validador.
Excepciones
| Excepción | Cuándo se lanza |
|---|---|
KeycloakAuthenticationException |
Credenciales del service account inválidas |
KeycloakConnectionException |
No se puede conectar con Keycloak (retornar 503 al cliente) |
KeycloakUserSyncException |
Error al crear/actualizar el usuario en el realm |
KeycloakConfigurationException |
Opciones inválidas (BaseUrl, Realm, ClientId, ClientSecret vacíos) |
Limitaciones net461
ServerCertificateCustomValidationCallbackno disponible. Para certificados auto-firmados en desarrollo:ServicePointManager.ServerCertificateValidationCallback = (s,c,ch,e) => true;(no usar en producción).- La validación de tokens es local (JWKS cacheado). No se llama a Keycloak en cada request.
Configuración mínima de Keycloak
- Crear un Client con
Client authentication: ONyService accounts roles: ✅ - Asignar roles al service account:
manage-users,view-users,impersonation - Protocol Mappers por cada claim custom: tipo
User Attribute,Add to access token: ✅ - (Opcional) Habilitar Token Exchange:
KC_FEATURES=token-exchange
Licencia
MIT — ver licencia
Desarrollado por By The Wave (BTW)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET Framework | net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
-
.NETFramework 4.6.1
- Microsoft.Extensions.Caching.Memory (>= 6.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- Microsoft.IdentityModel.Protocols.OpenIdConnect (>= 6.35.0)
- Microsoft.Owin.Security.Jwt (>= 4.2.2)
- System.IdentityModel.Tokens.Jwt (>= 6.35.0)
- System.Net.Http.Json (>= 6.0.1)
- System.Text.Json (>= 6.0.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.