Consideraciones al crear API JWT en .NET Core 9 (2025)
Guía práctica, actualizada y orientada a producción para diseñar, proteger, documentar y operar APIs con JWT sobre ASP.NET Core 9 en 2025.
TL;DR (para SEO y decisión rápida)
- Usa
Microsoft.AspNetCore.Authentication.JwtBearerpara validar tokens en tu API; configuraIssuer,Audiencey claves con rotación. - No generes tokens “caseros” para producción; apóyate en OIDC/OAuth 2.x (por ejemplo, Microsoft Entra ID, Auth0, Duende).
- Refuerza con Rate Limiting, Output Caching, CORS bien configurado y Health Checks.
- Documenta con OpenAPI nativo en .NET 9 y sirve Swagger UI o Scalar.
- Sigue las buenas prácticas OWASP y RFC (7519 para JWT, 9449 DPoP; evalúa mTLS de RFC 8705; observa avances de OAuth 2.1).
Índice
- #que-es-jwt-2025
- Arquitectura de autenticación/autorización con ASP.NET Core 9
- Buenas prácticas de seguridad (RFC + OWASP)
- Configuración de JwtBearer en .NET 9 (código)
- Tokens de desarrollo con
dotnet user-jwts - Autorización: roles, políticas y Minimal APIs
- OpenAPI en .NET 9 + Swagger/Scalar con Bearer
- CORS, Rate Limiting, Output Caching y Health Checks
- Gestión de secretos y Data Protection (Azure Key Vault)
- OAuth 2.1, DPoP y mTLS: cuándo aplicarlos
- Checklist de despliegue (2025)
- FAQ
- Gancho final para acelerar tu implementación
1) ¿Qué es un JWT y por qué sigue vigente en 2025?
Un JSON Web Token (JWT) es un formato compacto para transportar claims (afirmaciones) firmado digitalmente y opcionalmente cifrado. Estandarizado en RFC 7519, sigue siendo el mecanismo de facto para tokens de acceso e ID tokens en ecosistemas OAuth 2.x y OpenID Connect.
En 2025, la industria continúa endureciendo su uso con mejores prácticas (borrador BCP actualizado) y extensiones de prueba de posesión (DPoP) para reducir ataques por replay.
Nota clave: JWT es formato, no protocolo. La emisión y uso seguro vienen de OAuth 2.x/OIDC.
2) Arquitectura de autenticación y autorización en ASP.NET Core 9
ASP.NET Core 9 contempla autenticación mediante handlers (p. ej., JwtBearerHandler) y autorización basada en políticas y roles. La guía oficial detalla esquemas, middlewares y el orden correcto en el pipeline.
Para APIs que consumen access tokens emitidos por un IdP (Microsoft Entra ID, Auth0, etc.), el patrón típico es:
- El cliente obtiene un access token vía OAuth 2.x/OIDC.
- La API valida firma, issuer (
iss), audience (aud), lifetime (exp/nbf) y scopes/roles. - Se aplican autorizaciones por políticas/roles al endpoint.
Si trabajas con Microsoft Entra, valida siempre aud/iss y evita que clientes interpreten el token (trátalo como opaco desde el cliente).
3) Buenas prácticas de seguridad con JWT (OWASP + IETF)
- Algoritmos robustos y claves fuertes; no aceptes algoritmos del header sin whitelist (mitiga algorithm confusion).
- Verifica sistemáticamente firma, exp, nbf, iss, aud.
- Considera DPoP (RFC 9449) o mTLS (RFC 8705) para sender-constraining en escenarios de alto riesgo.
- Evita autogenerar tus propios access tokens para producción; usa OAuth/OIDC del proveedor.
- Sigue el borrador BCP (2025) con recomendaciones actualizadas de JWT.
4) Configuración de JwtBearer en .NET 9 (código paso a paso)
Instala el paquete oficial:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
Configura en Program.cs (ejemplo Minimal APIs):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// 1) Autenticación JWT Bearer
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// Valores típicos cuando validas tokens de un IdP
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
// Si usas RS256 desde tu IdP, usa IssuerSigningKeys a partir del JWKS
// y deja que el handler los descargue vía metadata (Authority).
};
// Si usas un IdP OIDC, aprovecha Authority/Metadata para descubrir JWKS:
options.Authority = builder.Configuration["Jwt:Authority"]; // https://login.microsoftonline.com/{tenantId}/v2.0
options.Audience = builder.Configuration["Jwt:Audience"];
});
// 2) Autorización (políticas/roles según tu dominio)
builder.Services.AddAuthorization();
// 3) OpenAPI nativo (.NET 9)
builder.Services.AddOpenApi();
var app = builder.Build();
app.UseHttpsRedirection();
// Orden correcto
app.UseAuthentication();
app.UseAuthorization();
// OpenAPI/Swagger/Scalar sólo en Dev
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapGet("/secure", (HttpContext http) =>
{
// Acceder al claim del usuario autenticado
var userId = http.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
return Results.Ok(new { message = "OK", userId });
}).RequireAuthorization();
app.Run();
Notas
- La guía oficial de JwtBearer recomienda no emitir tus propios tokens para producción y apoyarte en OIDC/OAuth.
- La API Browser documenta las sobrecargas de
AddJwtBearerdisponibles.
Para Microsoft Entra valida audiencia, emisor y firma conforme a su documentación de claims y validación.
5) Tokens de desarrollo con dotnet user-jwts
En desarrollo, puedes emitir JWT locales para probar endpoints protegidos sin levantar un IdP completo:
dotnet user-jwts create --role Admin --scope api.read --valid-for 1h
El comando admite roles, scopes, audience, issuer y caducidad.
En .NET 9 se reportaron incidencias con
user-jwts(claves/issuer); revisa notas de issues para ajustes de ValidIssuers/kid si te encuentras con “signature key was not found”.
6) Autorización con políticas, roles y Minimal APIs
Minimal APIs soporta [RequireAuthorization] con políticas o definición inline por endpoint:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CanRead", p => p.RequireClaim("scope", "api.read"));
app.MapGet("/data", () => Results.Ok(new [] { "A","B" }))
.RequireAuthorization("CanRead");
La referencia oficial cubre autenticación/autorización en Minimal APIs y cómo encadenarlo por endpoint.
Si necesitas especificar schemes o construir políticas inline, puedes usar el overload de RequireAuthorization para agregar schemes y requisitos.
7) OpenAPI en .NET 9 + Swagger/Scalar con Bearer
.NET 9 integra OpenAPI de forma nativa: AddOpenApi() + MapOpenApi() generan el documento 3.1 en /openapi/v1.json. Puedes servir Swagger UI y/o Scalar en desarrollo:
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // /openapi/v1.json
// Swagger UI (Swashbuckle)
app.UseSwaggerUI(o =>
{
o.SwaggerEndpoint("/openapi/v1.json", "v1");
// o.OAuthUsePkce(); // si vas a integrar OAuth2 en UI
});
// Scalar (opcional)
// app.MapScalarApiReference();
}
Para añadir Bearer como esquema de seguridad en Swagger/Swashbuckle, configura el SecurityDefinition/Requirement. (Si usas Scalar, puedes agregar el esquema y preferir Bearer; ver ejemplo y discusión en la comunidad).
8) CORS, Rate Limiting, Output Caching y Health Checks
CORS (Cross-Origin Resource Sharing)
Habilítalo explícitamente con orígenes/metodos/headers permitidos. Orden de middleware correcto es clave.
var cors = "_corsApi";
builder.Services.AddCors(o => o.AddPolicy(cors, p =>
p.WithOrigins("https://tu-frontend.com")
.AllowAnyHeader()
.AllowAnyMethod()
));
app.UseCors(cors);
Rate Limiting (throttling server-side)
Desde .NET 7 existe middleware nativo de Rate Limiting (TokenBucket, SlidingWindow, etc.), con ejemplos para particionar por usuario/IP y callbacks OnRejected.
using System.Threading.RateLimiting;
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("fixed", opt =>
{
opt.PermitLimit = 100; // 100 req
opt.Window = TimeSpan.FromMinutes(1); // por minuto
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 0;
});
});
app.UseRateLimiter();
app.MapGet("/secure", () => "ok")
.RequireAuthorization()
.RequireRateLimiting("fixed");
También puedes usar
System.Threading.RateLimitingen HttpClient (client-side).
Output Caching (rendimiento)
El Output Caching middleware de ASP.NET Core 9 permite cachear respuestas del servidor por endpoint o políticas. Recuerda su colocación en el pipeline y que difiere del Response Caching tradicional.
builder.Services.AddOutputCache();
app.UseOutputCache();
app.MapGet("/list", () => GetData())
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(30)));
Health Checks
Expón /health para liveness/readiness, y añade probes a dependencias (BD, colas, etc.).
builder.Services.AddHealthChecks();
app.MapHealthChecks("/health");
9) Gestión de secretos y Data Protection (Azure Key Vault)
- Nunca hardcodees secretos. Usa Azure Key Vault como Configuration Provider o para proteger el key ring de Data Protection.
- Si ejecutas múltiples instancias (contenedores/pods), persiste y protege el key ring fuera del contenedor y cifra en reposo (p. ej., Key Vault + Blob).
La guía oficial de Data Protection cubre opciones de persistencia y encryptors (Key Vault).
10) OAuth 2.1, DPoP y mTLS en 2025: ¿cuándo aplicarlos?
- OAuth 2.1 (borrador activo en 2025) consolida prácticas seguras: PKCE obligatorio, elimina flujo implícito, y aclara defaults modernos. Úsalo como guía de endurecimiento si diseñas o auditas flujos.
- DPoP (RFC 9449): sender-constraining a nivel aplicación; ideal para SPAs/clientes públicos que quieren mitigar replay de access/refresh tokens.
- mTLS (RFC 8705): si controlas clientes (B2B, backend-to-backend) y necesitas cert-bound tokens y/o client auth fuerte.
11) Checklist de despliegue (2025)
- Tokens: valida firma/iss/aud/exp/nbf y scopes/roles; no decodifiques en clientes.
- Claves: usa JWKS del IdP y rota; configura Authority/Audience.
- OpenAPI: expón
/openapi/v1.json(.NET 9), añade Swagger/Scalar sólo en Dev; esquema Bearer definido. - CORS: sólo orígenes necesarios; orden de middleware correcto.
- Rate Limiting: aplica políticas globales y por endpoint (429); monitoriza métricas.
- Caching: Output Caching para GET idempotentes; invalida correctamente.
- Health Checks:
/health+ probes a dependencias. - Secretos: Azure Key Vault/Managed Identity; Data Protection protegido y compartido.
- Hardening: considera DPoP/mTLS para escenarios de alto riesgo; sigue OWASP/JWT BCP.
- Logs y observabilidad: registra fallos de authz/authn y 401/403; integra con tu APM.
12) FAQ
¿Conviene emitir yo mismo los JWT?
Para producción, no. Generar tus propios access tokens suele introducir riesgos y no se alinea con estándares; usa OIDC/OAuth de un IdP.
¿Cómo valido un token de Microsoft Entra?
Verifica issuer, audiencia, firma (kid/JWKS) y claims relevantes; sigue la guía de claims validation y troubleshooting de firma.
¿Minimal APIs soporta authz granular por endpoint?
Sí. Aplica .RequireAuthorization() o políticas por endpoint; consulta la guía de Minimal APIs.
¿Qué diferencia hay entre Response Caching y Output Caching?
El Output Caching (servidor) es más flexible y configurable; Response Caching depende de headers HTTP y comportamiento de clientes/proxies.
¿OpenAPI en .NET 9 reemplaza a Swagger?
.NET 9 genera el documento con Microsoft.AspNetCore.OpenApi y puedes seguir usando Swagger UI/Scalar para la UI.
13) Ejemplo completo (plantilla “listo para despegar”)
Minimal API con JWT Bearer, OpenAPI, CORS, Rate Limiting, Output Caching y Health Checks.
// TargetFramework: net9.0
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.IdentityModel.Tokens;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
// --- AuthN: JwtBearer ---
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Authority = builder.Configuration["Jwt:Authority"];
o.Audience = builder.Configuration["Jwt:Audience"];
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
};
});
// --- AuthZ: políticas ---
builder.Services.AddAuthorizationBuilder()
.AddPolicy("CanRead", p => p.RequireClaim("scope", "api.read"));
// --- OpenAPI nativo ---
builder.Services.AddOpenApi(); // /openapi/v1.json
// --- CORS ---
const string Cors = "_cors";
builder.Services.AddCors(o => o.AddPolicy(Cors, p =>
p.WithOrigins("https://tu-frontend.com").AllowAnyHeader().AllowAnyMethod()
));
// --- Rate Limiting ---
builder.Services.AddRateLimiter(opts =>
{
opts.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
opts.AddSlidingWindowLimiter("api", o =>
{
o.PermitLimit = 100; // 100 req
o.Window = TimeSpan.FromMinutes(1);
o.SegmentsPerWindow = 4;
o.QueueLimit = 0;
});
});
// --- Output Caching ---
builder.Services.AddOutputCache();
// --- Health Checks ---
builder.Services.AddHealthChecks();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors(Cors);
app.UseRateLimiter();
app.UseOutputCache();
app.UseAuthentication();
app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // /openapi/v1.json
app.UseSwaggerUI(o => // UI para pruebas locales
o.SwaggerEndpoint("/openapi/v1.json", "v1"));
}
app.MapHealthChecks("/health");
// Endpoint protegido + cacheado + rate limited
app.MapGet("/products", () =>
{
// Simula datos
return Results.Ok(new[] { "Laptop", "Mouse", "Keyboard" });
})
.RequireAuthorization("CanRead")
.RequireRateLimiting("api")
.CacheOutput(p => p.Expire(TimeSpan.FromSeconds(30)));
app.Run();
- JwtBearer + Authority/Audience: deja que la metadata OIDC resuelva claves (JWKS).
- OpenAPI nativo:
/openapi/v1.json; puedes acoplar Swagger UI/Scalar. - CORS, Rate Limiting, Output Caching, Health Checks: patrones recomendados en ASP.NET Core 9.
14) Errores comunes (y cómo evitarlos)
- Aceptar cualquier algoritmo del header
alg→ Forzar RS/ES y lista de algoritmos aceptados. - No validar
aud/iss→ Imprescindible en APIs multi‑tenant/Entra. - Parsear tokens en el cliente → Trátalos como opacos fuera de la API.
- Swagger/Scalar en producción con auth relajada → limita a Dev, o protege con auth y CORS.
- Sin Rate Limiting en endpoints críticos → aplica políticas (429).
- Key ring de Data Protection local en entornos clusterizados → persiste y protege en Key Vault/Blob.
15) Más allá del Bearer: sender‑constraining y pruebas de posesión
Para APIs de alto valor (pagos, PII), añade DPoP o mTLS:
- DPoP: el cliente firma cada solicitud con su clave; el access token queda atado a esa clave, mitigando replay.
- mTLS: tokens certificate‑bound, defensa robusta para B2B.
16) Palabras clave (SEO)
API JWT, .NET 9, ASP.NET Core 9, autenticación JWT, autorización por políticas, OpenAPI, Swagger, Scalar, Rate Limiting, Output Caching, CORS, Health Checks, Azure Key Vault, Data Protection, OAuth 2.1, DPoP, mTLS, Microsoft Entra ID.
Meta descripción: Todo lo que necesitas en 2025 para crear una API JWT segura y performante en .NET Core 9: configuración JwtBearer, OpenAPI nativo, CORS, Rate Limiting, Output Caching, Health Checks, Key Vault, y mejores prácticas OWASP/RFC.
17) Gancho final (oferta irresistible)
¿Quieres que tu API JWT en .NET 9 esté lista para producción en 7 días?
Te propongo un “JWT Hardening Sprint”: auditamos tu issuer/audience, claims, Rate Limiting, Output Caching, CORS, OpenAPI y Key Vault; entregamos un pull request con configuraciones seguras, una política de rotación de claves, y un playbook de observabilidad y respuestas 401/403.
Escríbeme y agendamos un diagnóstico de 30 minutos sin costo (revisamos tuProgram.csy tu documento OpenAPI).
Referencias
- JWT Bearer en ASP.NET Core 9 (configuración, recomendaciones)
- AddJwtBearer API Browser (overloads)
- Autenticación en ASP.NET Core (conceptos, middlewares)
- Autorización en ASP.NET Core (políticas/roles)
- Minimal APIs: seguridad y authz por endpoint
- OpenAPI en .NET 9: generar/usar documentos; UI con Swagger/Scalar
- Rate Limiting y ejemplos en ASP.NET Core 9
- Output Caching en ASP.NET Core 9
- CORS en ASP.NET Core 9
- Health Checks (liveness/readiness)
- Data Protection & Azure Key Vault (configuración/seguridad)
- RFC 7519 (JWT), DPoP RFC 9449, mTLS RFC 8705, OAuth 2.1 (borrador 2025)
- OWASP JWT Cheat Sheet (buenas prácticas)
- Microsoft Entra: validación de claims, tokens y troubleshooting de firma










