Un JWT es un formato compacto para transmitir claims firmados entre dos partes.
En muchas aplicaciones web tradicionales (MVC, Razor Pages), la autenticación suele funcionar con cookies. El navegador envía la cookie en cada petición y el servidor la valida.
En el mundo de las APIs REST, también puedes usar cookies en algunos escenarios, pero es muy habitual usar tokens Bearer cuando tienes clientes móviles, SPAs, integraciones externas o varios servicios hablando entre sí.
- Stateless (sin estado): la API no necesita guardar en memoria quién está conectado. Si tienes 5 servidores balanceados, cualquiera puede validar el token.
- Clientes diversos: una app móvil, una SPA o un dispositivo IoT no siempre gestionan cookies igual que un navegador clásico.
Una alternativa es que el cliente envíe una credencial Bearer en cada petición. Un formato muy habitual para esa credencial es JWT (JSON Web Token).
Hoy vamos a ver cómo implementar un sistema de Login completo que genere estos tokens y cómo configurar ASP.NET Core para validarlos.
¿Qué es un JWT?
Un JWT es una cadena de texto como eyJhbGci..., codificada con Base64URL y dividida en tres partes separadas por puntos:
- Header: dice qué algoritmo de firma se usó (ej: HS256).
- Payload: Contiene los Claims (datos). Aquí dice quién eres (
sub: 123), cómo te llamas (name: Luis) y cuándo caduca el token (exp: 17000000). - Signature: permite comprobar la integridad y autenticidad del token mediante una clave simétrica o un par de claves asimétricas.
¡Cuidado! El payload no está cifrado, solo codificado con Base64URL. Cualquiera que obtenga el token puede leerlo. NUNCA guardes contraseñas o datos sensibles dentro del token. Solo identificadores y roles.
Instalación y configuración
Necesitamos el paquete oficial de Microsoft para trabajar con JWT.
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearerLa clave secreta (appsettings.json)
Necesitamos una clave para firmar los tokens. Debe ser larga, aleatoria y secreta.
Para aprender puedes verla en appsettings.json, pero en producción debería venir de variables de entorno, user-secrets, Azure Key Vault u otro almacén seguro.
{
"Jwt": {
"Key": "EstaEsUnaClaveSuperSecretaYDebeSerMuyLargaParaSeguridad",
"Issuer": "mi-api.com",
"Audience": "mi-app-frontend"
}
}Configurar el middleware (Program.cs)
Ahora le decimos a .NET: “Oye, cuando te llegue un token, verifica que la firma coincida con mi clave secreta”.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// --- CONFIGURACIÓN JWT ---
var jwtSettings = builder.Configuration.GetSection("Jwt");
var jwtKey = jwtSettings["Key"]
?? throw new InvalidOperationException("Falta Jwt:Key");
var key = Encoding.UTF8.GetBytes(jwtKey);
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true, // Verifica si ha caducado
ClockSkew = TimeSpan.Zero // Elimina la tolerancia de 5 minutos por defecto
};
});
// -------------------------
var app = builder.Build();
app.UseAuthentication(); // 👈 ¡Importante! Antes de Authorization
app.UseAuthorization();
app.MapControllers();
app.Run();Generar el token en el login
Vamos a crear el endpoint donde el usuario envía usuario/password y nosotros le devolvemos el token.
Lo ideal es tener esto en un servicio, pero para el ejemplo lo haremos en el controlador.
[HttpPost("login")]
public IActionResult Login([FromBody] LoginDto login)
{
// 1. Validar usuario (Esto iría contra BBDD)
if (login.Username != "admin" || login.Password != "1234")
{
return Unauthorized("Credenciales incorrectas");
}
// 2. Crear los Claims (Datos del usuario)
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, "1"), // ID del usuario
new Claim(ClaimTypes.Name, login.Username),
new Claim(ClaimTypes.Email, "[email protected]"),
new Claim(ClaimTypes.Role, "Admin") // Rol
};
// 3. Crear la firma
var jwtKey = _config["Jwt:Key"]
?? throw new InvalidOperationException("Falta Jwt:Key");
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
// 4. Generar el Token
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1), // Caduca en 1 hora
Issuer = _config["Jwt:Issuer"],
Audience = _config["Jwt:Audience"],
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var tokenConfig = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(tokenConfig);
return Ok(new { token = tokenString });
}La contraseña fija del ejemplo solo sirve para mostrar el flujo. En una aplicación real hay que validar un hash de contraseña con ASP.NET Core Identity o un mecanismo equivalente, además de aplicar límites de intentos y otras protecciones del login.
Proteger rutas
Ahora que el usuario tiene el token, debe enviarlo en cada petición en la cabecera Authorization:
Authorization: Bearer eyJhbGci...
Para proteger nuestros endpoints, usamos el atributo Authorize.
[Authorize] // 🔒 Solo usuarios con Token válido entran aquí
[HttpGet("perfil")]
public IActionResult GetPerfil()
{
// Podemos recuperar los datos del usuario desde el Token automáticamente
var usuario = User.Identity?.Name;
var id = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
return Ok($"Hola {usuario}, tu ID es {id}. Estás dentro.");
}Si intentas llamar a /perfil sin token (o con uno falso), recibirás un 401 Unauthorized.
Si envías el token correcto, recibirás el 200 OK.