Seguridad de Web API – JWT (Parte II de IV)
En la anterior entrada hablamos de la seguridad de WebApi con ApiKey. Hoy nos adentramos en Jason Web Token.
- Comprobación de ApiKey.
- Autenticación con Jason Web Token (JWT).
- Azure Active Directory mediante los secretos del registro de “Aplicaciones Empresariales”.
- Autenticación y Autorización con “Identity Server”.
Breve descripción de JWT
De JWT tenemos bastante información en internet, la definición oficial en español en la Wikipedia es basante completa pero creo que Santi Macías lo hace mejor en su blog. Resumiéndolo, tal vez demasiado, JWT es un estandar JSON para transmitir la identidad de un usuario de forma confiable, es decir, firmada y verificada digitalmente. Además también se incluye los privilegios que demanda, o en inglés «claims», como por ejemplo que su rol es de administrador, entre otras cosas. El contenido está dividido en tres partes, la cabecera con el tipo de codificación y token, el «payload» con los datos de la identificación e información útil, y la firma para verificar que el token es válido.
Si lo incluyo en los artículos de seguridad de WebApi es porque hoy en día es un estandar muy utilizado por los servicios de autenticación. Mi intención no es crer un servicio de autenticación completo, tan sólo cómo generar el token y cómo utilizarlo en una WebApi
Base del Proyecto
El proyecto va a utilizar las librerías de OWIN… esto me suena ¿pero qué es? «Open Web INterface» es una especificación que define las relaciones entre los servidores web y los componentes de las aplicaciones, reduciendo las interacciones entre éstas a un conjunto pequeño de tipos. Viéndolo desde una perpectiva práctica, utilizaremos la estructura que OWIN nos ofrece para incluir la gestión de la seguridad de la aplicación.
Esto se realiza mediante la clase «Startup.cs», en proyectos en «.Net Framework» hay que incluir en la raiz del proyecto un nuevo fichero de tipo «Clase de inicio OWIN» que depende de las librerías de NuGet y las instalará automáticamente si no las tiene:
- Owin
- Microsoft.Owin
En «.Net Core» esta clase viene por defecto incluida.
Para instalar las librerías que nos permitan trabajar con JWT se utiliza la siguiente línea de comandos:
PM> Install-Package System.IdentityModel.Tokens.Jwt
Que a su vez instalará los siguientes paquetes:
- Microsoft.IdentityModel.Logging
- Microsoft.IdentityModel.Tokens
- Microsoft.IdentityModel.JsonWebTokens
- System.IdentityModel.Tokens.Jwt
Por último para poder integrar JWT en OWIN se intalará el siguiente paquete de NuGet:
PM> Install-Package Microsoft.Owin.Security.Jwt
Este paquete también instalará:
- Microsoft.Owin.Security
- Microsoft.Owin.Security.OAuth
Ahora ya tenemos la base del proyecto preparado para poder codificar la solución.
Contrucción del código para generar JWT
Lo primero es crear una clase que retenga los datos que acreditan al usuario al que se le otorga la autorización. Para ellos genero la clase «Credencial.cs»
public class Credencial
{
public int IdUsuario { get; set; }
public string Nombre { get; set; }
public string CorreoElectronico { get; set; }
public IEnumerable<string> Roles { get; set; }
}
Antes de ponernos a configurar la seguridad del aplicativo codificaremos una clase que nos proporcione los «tokens». En la generación del «token» se incluye la firma digital. Para su composición se va a utlizar una clave o llave de seguridad que se encripta mediante un algoritmo simético, en este caso SHA256, para obtener un identificador único llamado «hash» y que nos proporciona la clave pública que será enviada en el «token». En este caso se crea una clase que extiende al tipo string
para poder trabajar con las textos que contengan las claves.
public static class ExtensionesSeguridad
{
public static SigningCredentials FormarSigningCredentials(this string jwtSecret)
{
var claveSimetrica = jwtSecret.FormarClaveSeguridadSimetrica();
var credencialesFirmadas = new SigningCredentials(claveSimetrica, SecurityAlgorithms.HmacSha256);
return credencialesFirmadas;
}
public static SymmetricSecurityKey FormarClaveSeguridadSimetrica(this string jwtSecret)
{
return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret));
}
}
Ahora se codifica la clase que proporciona los «tokens».
public class ProveedorJwt
{
#region Métodos Públicos
/// <summary>
/// Genera un token de tipo JSW Bearer
/// </summary>
/// <param name="credencial">Credencial de autenticación</param>
/// <param name="dominio">Dominio web donde se autentica</param>
/// <param name="secreto">Clave secreta para generar el hash</param>
/// <param name="caducidad">Tiempo que dura el token como activo</param>
/// <returns></returns>
public string GenerarToken(Credencial credencial, string dominio, string secreto, TimeSpan caducidad)
{
var jwtSeguro = new JwtSecurityToken
(
issuer: dominio,
audience: dominio,
claims: GenerarClaims(credencial),
// Cuando más corto sea el tiempo de caducidad más aumentará la seguridad
expires: DateTime.UtcNow.Add(caducidad),
signingCredentials: secreto.FormarSigningCredentials()
);
var token = new JwtSecurityTokenHandler().WriteToken(jwtSeguro);
return token;
}
#endregion
#region Métodos Auxiliares
/// <summary>
/// Genera una colección de claims basados en la credencial pasada como parámetro
/// </summary>
/// <param name="credencial">Credencial contiene la información del usuario</param>
/// <returns>Devuleve una colección de Claims para JWT</returns>
private IEnumerable<Claim> GenerarClaims(Credencial credencial)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, credencial.IdUsuario.ToString()),
new Claim(ClaimTypes.Name, credencial.Nombre),
// Se pueden añadir más claims aquí
};
if (credencial.Roles != null)
{
foreach (var role in credencial.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
}
return claims;
}
#endregion
}
Configuración de la seguridad en el proyecto
Ahora hay que aplicar JWT en la configuración del proyecto. Como anteriormente he dicho nos basaremos en OWIN o más concretamente en la clase Startup
.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
// AuthenticationType = "Bearer",
// Deben coincidir el secreto y el dominio al generar el token para el cliente
IssuerSigningKey = ConfigurationManager.AppSettings["SecretoJwt"].FormarClaveSeguridadSimetrica(),
ValidIssuer = ConfigurationManager.AppSettings["DominioJwt"],
ValidAudience = ConfigurationManager.AppSettings["DominioJwt"],
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
}
});
}
}
Y ahora hay que «decorar» el controlador que se quiera asegurar.
[Authorize]
public class ValuesController : ApiController
Consumiendo el controlador segurizado
Haremos las pruebas de dos formas, primero con la aplicación PostMan y luego con código C# añadiendo un nuevo controlador.
Para generar el «token» de PostMan utilizaré la consola de C# Interactiva. Si no la tenéis a mano podéis encontrarla aquí:

Lo primero es haber compilado el proyecto y luego agregar la librería mediante el comando #r y la ruta del archivo dll. Dejo el ejemplo que he realizado en mi equipo con el «token» generado.
> #r "C:\GitHub\RublenX\Aprendiendo\PoCSeguridadWebApi\JwtNetFrk\bin\JwtNetFrk.dll"
> JwtNetFrk.Seguridad.Credencial credencial = new JwtNetFrk.Seguridad.Credencial { IdUsuario = 1, Nombre = "JuanRu", CorreoElectronico = "inventado@email.es", Roles = new string[] { "Aplicacion" } };
> JwtNetFrk.Seguridad.ProveedorJwt proveedor = new JwtNetFrk.Seguridad.ProveedorJwt();
> string token = proveedor.GenerarToken(credencial, "localhost:44342", "YWPgvOaLJNcubpz4duqCS7KRwgmCVRRLQ2sVRjPVI7ELOFcGHl2", TimeSpan.FromHours(1));
> token
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjEiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiSnVhblJ1IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQXBsaWNhY2lvbiIsImV4cCI6MTU2MDk0NjMxNSwiaXNzIjoibG9jYWxob3N0OjQ0MzQyIiwiYXVkIjoibG9jYWxob3N0OjQ0MzQyIn0.jHPhvm__IfufYwlSYuK70LvuUSj7trNMS4uy6swv-q4"
>
Se configura PostMan con el «token» formado en Visual Studio de la siguiente manera:

Al lanzarlo podremos comprobar que devuelve los valores esperados.
Ahora probemos con C#. Se crea un nuevo controllador que solicitará por código el token, con una caducidad determinada, lo agregará al cliente y devolverá el resultado.
public class ConsumirJwtController : ApiController
{
public async Task<IHttpActionResult> Get()
{
// Se obtiene el token
ProveedorJwt jwTokenProv = new ProveedorJwt();
string token = jwTokenProv.GenerarToken(
new Credencial
{
IdUsuario = 1,
Nombre = "nombre usuario",
CorreoElectronico = "anonimo@email.es",
Roles = new List<string> { "apiAccess" }
},
ConfigurationManager.AppSettings["DominioJwt"],
ConfigurationManager.AppSettings["SecretoJwt"],
TimeSpan.FromHours(1));
HttpClient client = new HttpClient();
// Se incluye el token en el header de la petición
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage respuesta = await client.GetAsync("https://localhost:44342/api/Values");
if (!respuesta.IsSuccessStatusCode)
{
return BadRequest();
}
// Al estar redireccionándolo, sólo que con la autenticacón incluida, se utiliza ResponseMessage
return ResponseMessage(respuesta);
}
}
Conclusión
No me he dedicado a realiar una autenticación completa con gestión de usuarios, contraseñas y roles, tan sólo he mostrado cómo realizar un JWT, la configuración del proyecto para que pueda utilizarlo y cómo consumirlo.
Lo interesante de JWT es que no permite la modificación del mismo sin ser detectado y tiene una caducidad, aún así siempre hay que utilizar el protocolo seguro HTTPS para evitar ataques «Man-in-the-middle» .
Referencias
Openiddict : Using OpenIddict with JWT Bearer Authentication with a .NET Framework 4.6 Resource server
Santi Macias : Construyendo una Web API REST segura con JSON Web Token en .NET Parte I y Parte II
Jon Hilton : Secure your ASP.NET Core 2.0 API
Bit of Technology : ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app
Entradas relacionadas :