martes, 27 de febrero de 2018
Desde el punto de vista práctico, es una solución fantástica y muy cómoda de utilizar, y de hecho seguro que en más de una ocasión os habréis visto abusando de ella, es decir, habréis introducido en claims más información de la cuenta sólo por lo cómodo que resulta utilizarlo.
Pero claro, esto tiene su coste. Cada dato extra que añadimos a esos tokens supone transferencia adicional a la hora de recibir peticiones y retornar resultados, lo que puede repercutir en costes de operación si nuestro sistema tiene mucho tráfico, y en cualquier caso, aumentar los tiempos de respuesta de la aplicación.
Para solucionar estos problemas, ASP.NET Core incorpora el concepto de claims transformation, que son componentes ejecutados durante la fase de autenticación que toman el
ClaimsPrincipal
generado a partir de la información recibida en el token y permiten extenderlo o modificarlo para añadirle claims o atributos adicionales.De esta forma, podríamos introducir en los tokens que viajan por la red un único claim con el nombre o identificador único del usuario, y mediante una transformación posterior añadirle atributos adicionales, como su nombre completo, roles de seguridad y otros que nos hagan falta a nivel de aplicación.
Implementando claims transformations
Para añadir claims transformations lo único que tenemos que hacer es crear una clase que implementeIClaimsTransformation
y registrarla en el inyector de dependencias de ASP.NET Core.Este interfaz está definido en
Microsoft.AspNetCore.Authentication
de la siguiente manera:public interface IClaimsTransformation
{
Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
}
Como puede intuirse, el único método requerido TransformAsync()
recibe como argumento el ClaimsPrincipal
obtenido desde el token de seguridad por el middleware de autenticación de ASP.NET Core, y retorna un objeto ClaimsPrincipal
sobre el que ya habremos realizado las transformaciones oportunas.Por ejemplo, la siguiente clase hace añade un claim a todos los usuarios cuyo nombre sea “John” indicando que se trata de un usuario administrador de nuestra aplicación:
public class JohnIsAdminClaimsTransformation : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity)principal.Identity;
if (identity.Name.Equals("john", StringComparison.CurrentCultureIgnoreCase))
{
identity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
}
return Task.FromResult(principal);
}
}
Tras crear esta clase, para que eche a andar basta con registrarla como transient en el método ConfigureServices()
de la clase Startup
:public void ConfigureServices(IServiceCollection services)
{
[...]
services.AddTransient<IClaimsTransformation, JohnIsAdminClaimsTransformation>();
}
Como curiosidad, sólo podéis registrar un servicio de este tipo en el sistema. En caso contrario, se ejecutará únicamente el primero de ellos.Obviamente, nuestro transformador de claims es instanciado por el sistema de inyección de dependencias, por lo que podemos a su vez definir dependencias en su constructor. Esto nos dará acceso la posibilidad de implementar transformaciones bastante más interesantes.
Por ejemplo, en el siguiente código asumimos que el ID del usuario nos está llegando en el claim “nameidentifier” y lo utilizamos para recuperar información extra del usuario desde un almacén personalizado, que podría ser una base de datos, una caché, o el que nos venga bien:
public class MyAppClaimsTransformation : IClaimsTransformation
{
private readonly IMyUserStorage _userStorage;
public MyAppClaimsTransformation(IMyUserStorage userStorage)
{
_userStorage = userStorage;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var identity = (ClaimsIdentity) principal.Identity;
if (int.TryParse(identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value,
out var id))
{
var user = await _userStorage.GetUserByIdAsync(id);
foreach (var rol in user.Roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, rol));
}
identity.AddClaim(new Claim(ClaimTypes.Email, user.Email));
identity.AddClaim(new Claim(ClaimTypes.Name, user.FullName));
}
return principal;
}
}
Y así de sencillo, el usuario ya estará disponible en el resto de la aplicación con todos sus claims, tanto los que venían en el token original como los que hemos cargado adicionalmente nosotros con la transformación. Como de costumbre, podremos acceder a ellos desde la propiedad User
de controladores o del contexto HTTP, utilizar el atributo [Authorize]
para proteger el acceso a acciones, etc.Publicado en Variable not found.
Publicado por José M. Aguilar a las 8:55 a. m.
Etiquetas: aspnetcore, aspnetcoremvc, autenticación, jwt, trucos
2 Comentarios:
Hola!
Entiendo que en "var identity = (ClaimsIdentity) principal.Identity;"
identity almacena una "referencia" a principal.Identity ¿no?
Un saludo.
Hola!
Sí, pero ya con el tipo ClaimsIdentity para que sea más sencillo su uso posterior :)
Saludos!
Enviar un nuevo comentario