This page looks best with JavaScript enabled

Securizando tus servicios WebApi usando OWIN

 ·  ☕ 5 min  ·  ✍️ eiximenis

Nota: Este post ha sido importado de mi blog de geeks.ms. Es posible que algo no se vea del todo "correctamente". En cualquier caso puedes acceder a la versión original aquí

Hace algún tiempo escribí un post acerca de mecanismos para hacer tus servicios WebApi seguros. En este post mencionaba tres mecanismos para que tus servicios web solo sean accesibles a través de usuarios autenticados:

  • Atributo Authorize custom: no recomendado para autenticación (es para autorización).
  • Message Handler (DelegatingHandler): Mecanismo recomendado dentro de WebApi para autenticación.
  • HttpModule: Solución a nivel de IIS.

En el post comentaba que si tu aplicación depende de IIS puedes usar un HttpModule, pero que si quieres evitar tener una dependencia con él (ya que WebApi admite escenarios selfhost) debes usar un DelegatingHandler.

Pero este post está escrito antes de que OWIN fuese una realidad y con la aparición de OWIN existe otro mecanismo para autenticar nuestros servicios WebApi: usar un middleware OWIN.

Usar un middleware OWIN une las dos ventajas de los Message Handlers y los HttpModules:

  1. Al igual que un HttpModule funciona antes que WebApi por lo que autentica no solo llamadas a WebApi si no también llamadas a otros componentes (p. ej. MVC) que podamos tener.
  2. Al igual que un DelegatingHandler no está ligado a IIS en tanto que OWIN no está ligado a ningún servidor web en concreto.

El objetivo de este post es ver como crear un middleware OWIN para autenticar nuestros servicios WebApi, aunque si usáramos algún otro componente compatible con OWN (p. ej. NancyFx) nos serviría para autenticar las llamadas a ese otro componente también.

Vamos a empezar creando una aplicación ASP.NET Empty con WebApi agregado:

image

Luego agregaremos los paquetes Katana (para los despistados: Katana es una implementación de paquetes OWIN desarrollada por MS). En concreto instalaremos con NuGet el paquete Microsoft.Owin.Host.SystemWeb y este instalará ya todas las dependencias.

Creando el middleware OWIN

Un middleware OWIN es algo extraordinariamente simple. Es tan simple que no es ni una clase. Es tan solo un delegado. En su forma más básica un middleware OWIN es un Func<IDictionary<string, object>, Task>.

Aunque podríamos registrar directamente un delegado con esa firma, el sistema nos permite también registrar una clase, siempre y cuando tenga la siguiente estructura:

  1. using AppFunc = Func<IDictionary<string, object>, Task>;
  2. public class BasicAuthMiddleware
  3. {
  4.     private readonly AppFunc _next;
  5.     public BasicAuthMiddleware(AppFunc next)
  6.     {
  7.  
  8.     }
  9.  
  10.     public async Task Invoke(IDictionary<string, object> environment)
  11.     {
  12.  
  13.     }
  14. }

Este es el esqueleto básico para crear un middleware OWIN. Ahora hagamos que este middleware haga algo. Lo que hará será validar que existe autenticación básica (es decir que la cabecera HTTP Authorization llega y tiene el valor “Basic XXXX” donde XXXX es la cadena Base64 resultado de concatenar un login y un password separados por dos puntos (:).

Para ser súper genérico y no atarse a nadie, OWIN no define clases para acceder a las cabeceras HTTP, al cuerpo HTTP ni a nada. En su lugar tan solo se define el entorno: un diccionario de claves (cadena) valores (objeto). Pocas cosas hay más genéricas

que eso.

¿Y qué valores tienen esas claves? Pues hay algunas que define la propia especificación, pero luego tu módulo OWIN podría añadir las que quisiera… no dejan de ser un mecanismo de comunicación entre middlewares.

Veamos ahora el código del método

El código del constructor de la clase es trivial (simplemente nos guardamos en _next el valor del parámetro que recibimos).

Veamos el código del método Invoke:

  1. public async Task Invoke(IDictionary<string, object> environment)
  2. {
  3.     var auth = GetAuthHeader(environment);
  4.     if (!string.IsNullOrEmpty(auth))
  5.     {
  6.         if (auth.StartsWith("Basic "))
  7.         {
  8.             auth = auth.Substring("Basic ".Length);
  9.             var values = DecodeAuthValue(auth);
  10.             // Validaramos login (values.Item1) y pwd (values.Item2) aqu.
  11.             // En este ejemplo suponemos que cualquier combinacin es OK
  12.             PutAuthenticationInfo(environment, values.Item1);
  13.         }
  14.     }
  15.     await _next.Invoke(environment);
  16. }

Simplemente recogemos el valor del header Authorization y si existe y tiene el formato esperado (Basic ) lo decodificamos y colocamos la identidad del usuario en el contexto de OWIN.

El método GetAuthHeader obtiene el valor de la cabecera Authorization:

  1. private string GetAuthHeader(IDictionary<string, object> environment)
  2. {
  3.     var headers = environment["owin.RequestHeaders"] as IDictionary<string, string[]>;
  4.     if (headers != null && headers.ContainsKey("Authorization"))
  5.     {
  6.         return headers["Authorization"].FirstOrDefault();
  7.     }
  8.     return null;
  9. }

Los headers se almacenan dentro del contexto en la clave owin.RequestHeaders y el valor de dicha clave es un diccionario con todos los headers. Dado que un header puede tener varios valores el diccionario es de cadena (nombre del header) a valor (array de cadenas). En este caso devolvemos tan solo el primer que haya.

El método DecodeAuthValue simplemente decodifica la cadena Base64 y devuelve una tupla con el login y el password que estaban en la cabecera:

  1. private Tuple<string, string> DecodeAuthValue(string auth)
  2. {
  3.     var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(auth));
  4.     var tokens = decoded.Split(':');
  5.     return new Tuple<string, string>(tokens[], tokens.Length > 1 ? tokens[1] : null);
  6. }

Y finalmente el método PutAuthenticationInfo es el que crea el IPrincipal y lo coloca dentro del contexto de OWIN. Ya no debemos colocarlo en HttpContext.Current ni Thread.CurrentPrincipal. Debemos colocarlo dentro de la clave “server.User” del entorno OWIN:

  1. private void PutAuthenticationInfo(IDictionary<string, object> environment, string user)
  2. {
  3.     var claim = new Claim(ClaimTypes.Name, user);
  4.     var identity = new ClaimsIdentity(new[] {claim}, "Basic");
  5.     environment["server.User"] = new ClaimsPrincipal(identity);
  6. }

En este caso simplemente creamos un ClaimsPrincipal (con el nombre del usuario) y lo colocamos dentro del contexto.

El último punto es  configurar OWIN para que use nuestro middleware. Añade una clase, llámala Startup y coloca el siguiente código:

  1. [assembly: OwinStartup(typeof(OwinSecureDemo.Startup))]
  2.  
  3. namespace OwinSecureDemo
  4. {
  5.     public class Startup
  6.     {
  7.         public void Configuration(IAppBuilder app)
  8.         {
  9.             app.Use(typeof (BasicAuthMiddleware));
  10.         }
  11.     }
  12. }

Finalmente para probar creamos un controlador WebApi y lo decoramos con [Authorize]:

  1. [Authorize]
  2. public class BeersController : ApiController
  3. {
  4.     public IEnumerable<string> Get()
  5.     {
  6.         return
    new[] {"Estrella", "Voll Damm"};
  7.     }
  8. }

¡Y listos! Ya puedes probarlo pasando un valor válido a la cabecera Authorization y ver que todo funciona:

curl “http://localhost:23850/api/Beers" -H “Accept: application/json” -H “Authorization: Basic ZWl4aW1lbmlzOnB3ZA==”

Luego quitas la cabecera Authorization de la llamada y deberías recibir un error de usuario no autenticado.

Hemos visto lo sencillo que es usar un middleware OWIN para autenticación y además lo hemos implementado desde cero (sin usar ninguna clase dependiente de Katana tal como OwinContext u OwinRequest). Katana ofrece clases wrappers sobre el contexto OWIN para no tener que andar metiendo claves “a mano” en el diccionario que es el contexto de OWIN, pero eso ya sería motivo de otro post.

Un saludo!

Si quieres, puedes invitarme a un café xD

eiximenis
ESCRITO POR
eiximenis
Compulsive Developer