Buenas! El objetivo de este post es explicar la solución a la que he llegado para integrar autenticación con oAuth en un sitio web desarrollado con NancyFx. En este primer post veremos como hacerlo “de la forma clásica” pero en otro siguiente nos aprovecharemos de los componentes de autenticación de Katana.
Iré hablando en este blog más sobre OWIN, Katana y también sobre NancyFx, pero por el momento algunas definiciones rápidas:
- OWIN: Especificación que indica como deben comunicarse los servidores web, el middleware web y las aplicaciones web en .NET.
- Katana: Implementación de varios componentes (hosts, servidores, middlewares varios) OWIN por parte de Microsoft.
- NancyFx: Un framework basado en el patrón MVC para desarrollar aplicaciones web y APIs REST. Por decirlo de algún modo NancyFx “compite” con ASP.NET MVC y con WebApi a la vez.
Para saber más de OWIN hay un par de posts en mi blog pero también una serie fenomenal del Maestro. Échales un ojo.
Creando una aplicación Nancy ejecutándose en IIS
Vamos a crear una aplicación web, y vamos a usar NancyFx en lugar de ASP.NET, pero usando IIS (IISExpress).
NancyFx viene en dos “sabores”: puede ser un HttpHandler de ASP.NET (así instalas y configuras NancyFx usando el web.config), pero también existe como componente OWIN, lo que permite su uso dentro de cualquier entorno OWIN.
Para usar NancyFx como HttpHandler de ASP.NET debes instalar el paquete Nancy.Hosting.Aspnet que es el que contiene todas las referencias a ASP.NET (NancyFx es totalmente independiente de ASP.NET). Ahora vamos a crear una aplicación web ASP.NET, así que lo lógico parece ser usar NancyFx como HttpHandler y listos. En muchos casos puede ser lo más lógico, pero no es lo que vamos a hacer. No vamos a usar NancyFx como HttpHandler de ASP.NET. En su lugar lo vamos a utilizar como componente OWIN y, para ello, nos aprovecharemos de un componente de Katana que permite usar componentes OWIN dentro del pipeline de ASP.NET (en terminología OWIN diríamos que este componente de Katana implementa un Host y un servidor Web OWIN). Esto parece dar muchas vueltas, y en muchos casos así puede ser, pero tiene las siguientes ventajas:
- Dado que nuestra aplicación será OWIN en cualquier momento podremos abandonar el cómodo regazo de IIS e irnos a cualquier otro host o servidor OWIN.
- Incluso aunque no tengamos pensado divorciarnos de IIS, podremos utilizar cualquier componente OWIN que haya por ahí 😉
Como digo, para poder utilizar NancyFx como componente OWIN pero ejecutándose en un pipeline de ASP.NET necesitamos la ayuda de Katana, en concreto del componente Microsoft.Owin.Host.SystemWeb.
Así, los pasos para usar NancyFx como componente OWIN dentro del pipeline de ASP.NET son los siguientes. Antes que nada crea un proyecto de tipo ASP.NET y selecciona el template “Empty”:
Luego instala los siguientes paquetes de NuGet:
- Nancy (el core de NancyFx)
- Nancy.Owin (Para usar Nancy como módulo OWIN)
- Microsoft.Owin.Host.SystemWeb (Para usar módulos OWIN en el pipeline de ASP.NET)
Con esto ya tenemos el esqueleto necesario.
El siguiente paso es crear la clase de inicialización de OWIN. Para ello agrega una clase normal y llámala Startup:
- public class Startup
- {
- public void Configuration(IAppBuilder app)
- {
- app.UseNancy();
- }
- }
Es importante que la clase se llame Startup. Esto es una convención que sigue Katana (pues quien inicializa los módulos OWIN es Katana a través del paquete Microsoft.Owin.Host.SystemWeb). Si nuestra clase tiene otro nombre recibirás un error como el siguiente:
Una solución es o bien renombrar la clase para que se llame Startup o en el caso de que no quieras hacerlo usar el atributo OwinStartupAttribute:
- [assembly: OwinStartup(typeof(MyCustomStartup))]
Si ahora ejecutas la aplicación deberías ver algo parecido a lo siguiente:
Eso significa que Nancy está funcionando correctamente (este 404 ha sido servido por Nancy). ¡Felicidades! Ya tienes a Nancy corriendo bajo IIS.
Vamos ahora a crear un módulo de Nancy (más o menos equivalente a un controlador de ASP.NET MVC), para gestionar la llamada a la URL / y mostrar una vista con solo el enlace “Login with twitter”. Añade una clase a tu proyecto con el siguiente código:
- public class MainModule : NancyModule
- {
- public MainModule()
- {
- Get["/"] = _ => View["main.html"];
- }
- }
Con esto has creado un módulo de NancyFx y has enrutado todas las peticiones que vayan a la URL “/” para que muestren la vista main.html. Añade pues un archivo main.html (no es necesario que esté en ninguna carpeta Views ni nada) que muestre un enlace a la URL “/authentication/redirect/twitter”.
Si ahora ejecutas el proyecto deberías se tendría que ver el contenido del fichero main.html. Y si pulsas sobre el enlace tienes que recibir el 404 de Nancy.
Perfecto! Ya tenemos el esqueleto base de la aplicación. Ahora vayamos a integrar la autenticación por twitter.
Integrando oAuth con NancyFx a la manera clásica
Nota: El contenido de este apartado presupone que tienes creada una aplicación en twitter y que por lo tanto tienes un consumer key y un consumer secret. También debes configurar la aplicación twitter para que la URL de callback sea /authentication/authenticatecallback">http://localhost:<puerto>/authentication/authenticatecallback
Nota 2: Twitter (y otros proveedores oAuth) no dejan dar de alta aplicaciones cuyo callback sea una dirección de localhost. En este caso lo más rápido es usar el dominio xxx.localtest.me (usa cualquier valor para xxx). El dominio localtest.me está especialmente pensado para estos casos: cualquier subdominio en localtest.me resuelve a 127.0.0.1 😉
NancyFx es un framework muy modular, y no tiene incorporado el concepto de autenticación o autorización. Eso significa que no hay por defecto ninguna API ni nada que nos diga si el usuario está autenticado o bien poder autenticarlo. Todo eso se deja a implementaciones “externas” al core de NancyFx.
P. ej. si queremos autenticar nuestra aplicación basándonos en cookies (lo que en el mundo ASP.NET conocemos como autenticación por forms) debemos instalar el paquete Nancy.Authentication.Forms. Hay otros paquetes para otros tipos de autenticación (como puede ser Nancy.Authentication.Basic para autenticación básica de HTTP o bien Nancy.Authentication.Stateless para basar la autenticación en alguna cabecera específica de la petición). Todos estos paquetes (y más que hay para otros tipos de autenticación) se basan en el modelo de extensibilidad de NancyFx.
Por supuesto hay un paquete para integrarnos con oAuth que antes respondía al interesante nombre de Nancy.Authentication.WorldDomination pero que ahora tiene el aburrido y anodino nombre de Nancy.SimpleAuthentication. Así que añade este paquete a tu proyecto y ya estarás listo para iniciar un flujo para autenticación.
Al añadir este paquete tu web.config se habrá modificado y se habrán añadido las siguientes líneas:
- <configSections>
- <section name="authenticationProviders" type="SimpleAuthentication.Core.Config.ProviderConfiguration, SimpleAuthentication.Core" />
- </configSections>
- <authenticationProviders>
- <providers>
- <add name="Facebook" key="please-enter-your-real-value" secret="please-enter-your-real-value" />
- <add name="Google" key="please-enter-your-real-value" secret="please-enter-your-real-value" />
- <add name="Twitter" key="please-enter-your-real-value" secret="please-enter-your-real-value" />
- <add name="WindowsLive" key="please-enter-your-real-value" secret="please-enter-your-real-value" />
- </providers>
- </authenticationProviders>
En nuestro caso podemos dejar solo la información del provider de Twitter y debeis rellenar el valor de consumer key y el consumer sectret que os provee Twitter. Acuérdate tambien de mover el tag <configSections> para que sea el primero dentro del <configuration> en el web.config, si no IIS os dará un error!
Ahora el siguiente paso es crear una clase que implemente IAuthenticationCallbackProvider:
- public class MyCustomAuthCallbackProvider : IAuthenticationCallbackProvider
- {
- public dynamic Process(NancyModule nancyModule, AuthenticateCallbackData model)
- {
- // En caso de autenticacin OK
- }
- public dynamic OnRedirectToAuthenticationProviderError(NancyModule nancyModule, string errorMessage)
- {
- // En caso de error
- }
- }
Ejecuta de nuevo tu aplicación. Ahora si pulsas sobre el enlace de login with twitter… ¡deberías ver la página de Twitter!:
Una vez el usuario haya dado sus datos y haya autorizado a tu aplicación entonces se ejecuta el método Process de la clase que acabamos de crear. En el parámetro “model” tendrás toda la información necesaria:
Una vez sabes cual es el usuario autenticado debes autenticarlo en tu propia web. Es decir Twitter nos ha dado el OK, pero ahora lo que nos falta es utilizar algún mecanismo para autenticar todas las peticiones que realice este usuario en nuestra web.
Nota: Para que todo funcione recuerda que la URL de callback de la aplicación en twitter debe apuntar a /authentication/authenticatecallback y que el enlace de “Login with twitter” debe apuntar a /authentication/redirect/twitter. Esas dos URLs son gestionadas automáticamente por el paquete WorldDomination.
Si queremos utilizar una cookie, tenemos que instalar el paquete Nancy.Authentication.Forms, así… que nada, a por él!
Añadiendo la seguridad por Forms
Una vez tenemos el paquete Nancy.Authentication.Forms añadido ya podemos configurarlo. Son necesarios 3 pasos para que todo funcione correctamente.
El primero es crear una clase que implemente la interfaz IUserIdentity. Esta interfaz es toda la información que el core de Nancy mantiene sobre un usuario autenticado (nombre y permisos):
- public class AuthenticatedUser : IUserIdentity
- {
- public string UserName { get; set; }
- public IEnumerable<string> Claims { get; set; }
- }
El segundo es crear un “User Mapper”. Esto vendría a ser el equivalente (solo lectura) del Membership Provider en ASP.NET. Es decir el encargado de obtener los datos del usuario del repositorio donde se guarden:
- public class MyUserMapper : IUserMapper
- {
- public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
- {
- // Obtendriamos el usuario de la BBDD
- return new AuthenticatedUser()
- {
- UserName = "eiximenis",
- Claims = new[] {"Read", "Write", "Admin"}
- };
- }
- }
En Nancy todos los usuarios se identifican por un Guid (por supuesto esto no significa que en la BBDD este Guid tenga que ser la PK de la tabla de usuarios!).
El tercer y último paso es indicar al core de Nancy que queremos autenticación por formularios:
- public class Bootstrapper : DefaultNancyBootstrapper
- {
- protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
- {
- base.RequestStartup(container, pipelines, context);
- var formsAuthConfiguration = new FormsAuthenticationConfiguration
- {
- DisableRedirect = true,
- UserMapper = new MyUserMapper()
- };
- FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
- }
- }
El Bootstrapper es la clase que inicializa todo el core de Nancy. Hasta ahora no teníamos, así que lo añadimos y listos (no hay que indicar en ningún sitio cual es nuestro Bootstrapper, se descubre automáticamente). Con esto ya tenemos la autenticación por forms habilitada en nuestro proyecto.
Volvamos ahora a nuestro CallbackProvider. Lo que haríamos en el método Process es:
- Consultar la BBDD de usuarios para encontrar el usuario que se corresponda con los datos que nos ha devuelto twitter.
- En caso de no existir crearlo y obtener su Guid.
- En caso de existir obtener su Guid.
- Una vez tenemos el Guid llamar al método LoginAndRedirect. Este método vendría a ser el equivalente al SetAuthCookie de ASP.NET:
- public dynamic Process(NancyModule nancyModule, AuthenticateCallbackData model)
- {
- // Accederiamos a BBDD para obtener el GUID del usuario
- // O si no lo crearamos
- var userGuid = Guid.NewGuid();
- return nancyModule.LoginAndRedirect(userGuid);
- }
¡Y listos! ¡Has terminado!
Añadir contenido securizado
Vamos a añadir un módulo a Nancy que solo se ejecute si el usuario está autenticado. En ASP.NET MVC meterías un [Authorize] en la acción del controlador. En Nancy lo equivalente es usar el module hook Before. Cada modulo de Nancy tiene una propiedad Before en la que puedes poner código que se ejecuta antes de que se ejecute cualquier otro código del módulo (es decir, el código correspondiente a la petición). Desde este código puedes verificar si el usuario está autenticado:
- public class SecureModule : NancyModule
- {
- public SecureModule()
- {
- Before += ctx =>
- {
- if (ctx.CurrentUser == null)
- return new Response()
- {
- StatusCode = HttpStatusCode.Unauthorized
- };
- return null;
- };
- Get["/Secure"] = _ => View["secure.html",
- new
- {
- Name = Context.CurrentUser.UserName
- }];
- }
- }
Si la propiedad del CurrentUser del contexto es igual a null devolvemos un 401. En caso contrario no hacemos nada y dejamos que siga el pipeline de Nancy (por lo que se ejecutará el código que toque según la URL).
Para terminar añade el archivo secure.html:
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title></title>
- </head>
- <body>
- Hello @Model.Name
- </body>
- </html>
(Aunque lo parezca esto NO es Razor. Es el view engine por defecto de Nancy que se llama SSVE (Super Simple View Engine)).
Ya estamos listos para probar! Ejecuta el proyecto y navega a /secure. Deberías ver una página sin nada, pero un vistazo a la pestaña Network de las developer tools nos informa que estamos teniendo un 401:
Pefecto. Vuelve a la raíz, y autentícate por twitter. Después del proceso de autenticación volverás a la raiz. Navega a /secure de nuevo y ahora deberías ver la vista secure.html:
(Recuerda que todos los usuarios se llamarán eiximenis ya que nuestro IUserMapper siempre devuelve lo mismo ;))
Y hemos finalizado! Ya tienes tu web con Nancy autenticada mediante oAuth. Hemos elegido twitter pero para el resto de proveedores no hay muchas diferencias (el mérito es todo de WorldDomination).
Unas palabras finales
Hemos utilizado Katana para usar NancyFx en “modo OWIN” bajo el pipeline de ASP.NET en IIS. Esto nos permitiría integrarnos con otro middleware OWIN. Así pues, y esta es precisamente la idea de OWIN, podría usar un middleware OWIN para autenticar y autorizar las peticiones. Es decir, no delegar la autenticación y la autorización en el propio NancyFx si no tener un módulo OWIN encargado precisamente de esto…
… en el siguiente post veremos como 😉
Saludos!
PD: Tienes el proyecto completo (VS2013) en http://sdrv.ms/18brcO7 (Archivo NancyKatanaIIS.zip)