JSON Web Token en ASP.NET Core 6
En este artículo, mostraré una breve introducción sobre JSON Web Token y también un ejemplo práctico de cómo se puede implementar JWT utilizando ASP.NET Core 6.
Qué es JSON Web Token
JSON Web Token (JWT) es un estándar abierto basado en JSON para crear un token que sirva para enviar datos entre aplicaciones o servicios y garantizar que sean válidos y seguros. Esta información se puede verificar y confiar porque está firmada digitalmente.
¿Cuándo usar JSON Web Token?
Cuando es útil JWT:
- Autorización, uso más común. Una vez que el usuario haya iniciado sesión, cada solicitud posterior incluirá el JWT, lo que permitirá al usuario acceder a rutas, servicios y recursos permitidos con ese token. Single Sign On (SSO) es una función que se usa ampliamente en JWT en la actualidad, debido a su pequeña sobrecarga y su capacidad para usarse fácilmente en diferentes dominios.
- Intercambio de información, los JWT son una buena manera de transmitir información de forma segura entre las partes. Debido a que los JWT se pueden firmar, por ejemplo, utilizando pares de claves pública/privada, puede estar seguro de que los remitentes son quienes dicen ser. Además, como la firma se calcula utilizando el encabezado y la carga útil, también puede verificar que el contenido no haya sido alterado.
¿Cómo funciona JSON Web Token?
En la autenticación, cuando el usuario inicia sesión correctamente con sus credenciales, se devolverá un token web JSON. Dado que los tokens son credenciales, se debe tener mucho cuidado para evitar problemas de seguridad. En general, no debe conservar los tokens más tiempo del necesario.
Cada vez que el usuario desee acceder a una ruta o recurso protegido, el agente de usuario debe enviar el JWT, generalmente en el encabezado de Autorización utilizando el esquema Bearer.
- La aplicación o el cliente solicita autorización al servidor de autorización.
- Cuando se otorga la autorización, el servidor de autorización devuelve un token de acceso a la aplicación.
- La aplicación usa el token de acceso para acceder a un recurso protegido (como una API).
Estructura de JSON Web Token
Un JWT está separado por puntos (HEADER.PAYLOAD.SIGNATURE)
en tres partes codificadas, un encabezado o header, un contenido o payload, y una firma o signature.
Podemos decodificar y ver el contenido del token utilizando jwt.io sin necesidad de saber la clave con la cual se ha generado, aunque no podremos validarlo sin la misma.
- Header: el tipo de token y el algoritmo usado para la firma.
{
"alg": "HS256", // HS256 indica que este token está firmado utilizando HMAC-SHA256.
"typ": "JWT"
}
- Payload: contiene los claims que son datos sobre una entidad (normalmente, el usuario) y datos adicionales. La información se proporciona como pares key/value (clave-valor). No incluir datos sensibles sin cifrar en los claims. Hay 3 tipos de claims:
- Claims registrados: claims predefinidos que no son obligatorios. Figuran en el IANA JSON Web Token Claim Register y cuyo propósito se establece en un estándar. Como iss (emisor), aud (receptores), exp (tiempo de expiración), sub (razón), entre otros.
- Claims públicos: pueden definirse a voluntad, ya que no están sujetos a restricciones. Para que no se produzcan conflictos en la semántica de los claims, es necesario registrar los claims públicamente en IANA JSON Web Token Claim Register o asignarles nombres que no puedan coincidir.
- Claims privados: Son los claims personalizados creados para compartir información entre partes que acuerdan usarlos y no son claims registrados ni públicos .. Al nombrarlos, es importante asegurarse de que no vayan a entrar en conflicto con ningún claim registrado o público.
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
- Signature: es una firma que nos permite verificar si el token es válido, para crear la parte de la firma, debe tomar el header codificado, el payload codificado, un secreto, el algoritmo especificado en el encabezado y firmarlo.
key = 'secret-key'
unsignedToken = base64Encode(header) + '.' + base64Encode(payload)
signature = SHA256(key, unsignedToken)
token = unsignedToken + '.' + signature
La firma se usa para verificar que el mensaje no se modificó en el camino y, en el caso de tokens firmados con una clave privada, también puede verificar que el remitente del JWT es quien dice ser.
// JSON Web Token (HEADER.PAYLOAD.SIGNATURE):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Buenas prácticas usando JSON Web Token
Al generar tokens:
- Emitir siempre tokens firmados
- Usar algoritmos criptográficos fuertes
- Poner fecha de expiración e identificador único
- Dar valor al emisor (issuer) y a los destinatarios (audience)
- No incluir datos sensibles sin cifrar en los claims
Al validar:
- No aceptar tokens sin firma
- Validar claims de cabecera
- Validar siempre emisor y destinatarios
- Almacenar las claves de firma por emisor y algoritmo
Comunicación:
- Comunicación entre partes con HTTPS para encriptar el tráfico
Veamos el código👇
Construiremos una aplicación que ilustre cómo usar JSON Web Token. Esta aplicación contendrá una API que genera el JWT y accederemos con este token a un recurso protegido.
Generando JSON Web Token
En primer lugar, el código donde generamos el JWT:
public async Task<string> GetTokenAsync(string userName)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:Key"]);
var issuer = _configuration["JwtSettings:Issuer"];
var audience = _configuration["JwtSettings:Audience"];
var user = await _userManager.FindByNameAsync(userName);
var roles = await _userManager.GetRolesAsync(user);
var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName) };
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
Audience = audience,
Subject = new ClaimsIdentity(claims.ToArray()),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
Código en la API donde comprobamos credenciales y obtenemos JWT:
[HttpPost("authenticate")]
public async Task<ActionResult<AuthenticateResponse>> Authenticate([FromForm] AuthenticateRequest request)
{
//...
var succeeded = await _userManager.CheckPasswordAsync(user, request.Password);
if (succeeded)
{
response.Succeeded = succeeded;
response.Token = await _tokenClaimsService.GetTokenAsync(request.UserName);
}
//...
}
Probando nuestra implementación
1. Solicita autorización al servidor de autorización y el servidor de autorización devuelve un token de acceso a la aplicación:
2. Introducimos este token para acceder a un recurso protegido (como una API):
3. Ahora ya podemos acceder a rutas, servicios y recursos protegidos:
Eso es todo por el momento, seguiré actualizando y agregando contenido en este post. Espero que te haya resultado interesante😉
Descargas
El código fuente de este artículo se puede encontrar en GitHub