Autorización en WebApi… ¿y si fuera como la de Asp.Net Core 1.0?

[Actualizado] El paquete de NuGet depende ahora de Microsoft.AspNet.Authorization (actualmente en versión 1.0.0-rc1-final) y esto implica algunos cambios. He actualizado el post para reflejarlos.

Una de las muchas novedades que nos ofrece Asp.Net Core 1.0 es el rediseño de los filtros de autenticación. Se deja atrás el concepto de autorización por roles o usuarios y se introduce un nuevo concepto de autorización por políticas. Cada una de estas políticas define que elementos quiere tener en cuenta para autorizar al usuario. Puede ser la presencia de un determinado claim, que un claim tenga uno o varios valores específicos, o incluso que el nombre del usuario o el rol tengan unos valores determinados (no olvidemos que los roles o el nombre del usuario siguen siendo claims ). Además se introduce también el concepto de un servicio de autorización (IAuthorizationService) que podremos inyectar en nuestros controladores para validar si un usuario está autorizado o no a realizar una acción, no sólo teniendo en cuenta las características del usuario, sino el propio estado del recurso al que se quiere acceder.

Estas políticas las definiremos de esta forma:

// Add Authorization policies
services.AddAuthorization(options =>
{
    options.AddPolicy("MiPolitica", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("department", "sales");
    });
    options.AddPolicy("Mayor18", policy =>
    {
        policy.Requirements.Add(new MinimumAgeRequirement(18));
    });
});

En Asp.Net Core 1.0, esta configuración se hace en la clase Startup, dentro del método ConfigureServices. Como seguramente ya sabrás, este método lo que hace es configurar y registrar una serie de clases para que estén disponibles para el mecanismo integrado de inyección de dependencias, y puedan ser utilizadas por la propia infraestructura e incluso por el propio desarrollador.

Una vez definidas las políticas, se define con el atributo Authorize la política que se quiere aplicar a nivel de controlador o acción. De hecho, se ha creado una sobrecarga en el constructor de este conocido atributo para poder definir el nombre de la política que se va a utilizar para validar la autorización.

[Authorize("MiPolitica")]

La verdad es que toda esta nueva infraestructura nos permite abordar escenarios realmente complejos que en las versiones actuales de WebApi y Mvc exigirían mucho código personalizado.

¿Lo puedo utilizar en WebApi hoy?

Pero toda esta infraestructura no está disponible en los proyectos de WebApi en los que estamos trabajando hoy. Tenemos que seguir utilizando el atributo Authorize de toda la vida en el que sólo podemos especificar los roles o los usuarios que están autorizados.

¿O no?

Gracias al paquete de NuGet Acheve.Web.Http.Authorization podemos tener a nuestra disposición en los proyectos de WebApi toda la potencia de la autorización basada en políticas que nos ofrece Asp.Net Core 1.0.

Sólo hay que tener en cuenta una cosa importante, y es que, por defecto, WebApi no nos proporciona un mecanismo de inyección de dependencias integrado en el framework, y, por lo tanto, para que funcione todo esto estamos obligados a configurar nuestro contenedor de inyección de dependencias preferido con WebApi y a registrar en el los tipos que hacen posible que todo esto funcione.

Configurar un contenedor de inyección de dependencias externo en WebApi es sencillo. Sólo tenemos que asignar el Adapter para nuestro contenedor a la propiedad DependencyResolver de la configuración de WebApi.

Por ejemplo con Autofac, la configuración sería algo parecida a esto:

public class Startup
{
  public void Configuration(IAppBuilder app)
  {
    var builder = new ContainerBuilder();

    // STANDARD WEB API SETUP:

    // Get your HttpConfiguration. In OWIN, you'll create one
    // rather than using GlobalConfiguration.
    var config = new HttpConfiguration();

    // Register your Web API controllers.
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

    // Run other optional steps, like registering filters,
    // per-controller-type services, etc., then set the dependency resolver
    // to be Autofac.
    var container = builder.Build();
    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    // OWIN WEB API SETUP:

    // Register the Autofac middleware FIRST, then the Autofac Web API middleware,
    // and finally the standard Web API middleware.
    app.UseAutofacMiddleware(container);
    app.UseAutofacWebApi(config);
    app.UseWebApi(config);
  }
}

A partir de este momento, nuestros controladores pueden definir dependencias en el constructor que serán resueltas por el contenedor.

Si instalamos el paquete de NuGet Acheve.Web.Http.Authorization.Autofac, podremos utilizar un método de extensión sobre el ContainerBuilder de Autofac que nos permitirá registrar nuestras políticas exactamente de la misma manera que en Asp.Net Core 1.0.

builder.UsePolicyAuthorization(options =>
{
    options.AddPolicy(Policies.Sales, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireClaim("department", "sales");
    });
    options.AddPolicy(Policies.Over18Years, policy =>
    {
        policy.Requirements.Add(new MinimumAgeRequirement(18));   
    });
});

[Actualizado]
A partir de este momento, podremos usar los atributos definidos en el ensamblado Microsoft.Asp.Net.Authorization (este ya es Asp.Net Core!) para definir las políticas de autorización de nuestros controladores y métodos de acción. De hecho, el atributo se llama igual que el definido por WebApi, por lo que tendremos que utilizarlo definiendo el nombre completo del tipo.

    [Microsoft.AspNet.Authorization.Authorize]
    [RoutePrefix("products")]
    public class ProductsController : ApiController

Pero todavía nos falta una cosa. El nuevo modelo de Asp.Net Core permite que estos atributos no sean atributos de autorización del framework (lo que por otra parte nos permite reutilizarlos en una plataforma diferente). Así que si no hacemos nada más, realmente WebApi no sabría qué hacer con ellos. Para solucionarlo, configuraremos un filtro global en la configuración de WebApi que será el que se encargue de descubrir estos atributos e integrarlos en la autorización de WebApi.

config.Filters.Add(new UseAspNetCoreAuthorizationModelAttribute());

[Final actualizado]

Voy a ir escribiendo una serie de posts sobre el uso de toda esta nueva infraestructura de autorización para descubrir toda la potencia que nos ofrece. Pero lo más importante es que todo lo que veamos, lo vamos a poder utilizar tanto en WebApi 2 como en Asp.Net Core 1.0.

El código fuente de los paquetes de NuGet está aquí: https://github.com/hbiarge/Acheve.Web.Http.Authorization e incluye ejemplos de uso de este mecanismo de autorización en un proyecto de WebApi 2.