Autor en Google+
Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET, ASP.NET Core, MVC, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

12 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET, ASP.NET Core, MVC, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 27 de febrero de 2018
ASP.NET Core MVC Cuando en nuestras aplicaciones o APIs web usamos autenticación basada en tokens, ya sean JWT o simples cookies, normalmente aprovechamos para introducir en ellos información extendida sobre el usuario que realiza la petición, básicamente para tenerla más a mano a la hora de procesarla. Por ejemplo, suele ser habitual guardar su nombre, el identificador en base de datos, roles de seguridad y cosas de este estilo.

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 implemente IClaimsTransformation 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.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

2 Comentarios:

Joseba dijo...

Hola!

Entiendo que en "var identity = (ClaimsIdentity) principal.Identity;"

identity almacena una "referencia" a principal.Identity ¿no?

Un saludo.

José M. Aguilar dijo...

Hola!

Sí, pero ya con el tipo ClaimsIdentity para que sea más sencillo su uso posterior :)

Saludos!

Artículos relacionados: