Autenticación de usuarios en ASP.NET MVC - Problemas con web hosting en clúster y balanceo de carga

En ciertas ocasiones, necesitamos que nuestra aplicación web ASP.NET disponga de un sistema de autenticación básica de usuarios para realizar, por ejemplo, tareas administrativas de configuración a través de un módulo de administración integrado que solo sea accesible para usuarios autenticados. Para este escenario en concreto, bastaría con implementar un simple sistema de Login para que el o los encargados de administrar la aplicación puedan autenticarse mediante su usuario y contraseña en el sistema.

En este Post veremos cómo crear un sistema de Login basado en FormsAuthentication con encriptado de contraseña, y como integrarlo en una aplicación web ASP.NET MVC. También veremos como solucionar los problemas derivados de publicar nuestra aplicación en un Web hosting en clúster con balanceo de carga.

Nota: Antes de comenzar a desarrollar el Post, comentar que este ejemplo está desarrollado en ASP.NET MVC4 Framework 4.0 aunque también es perfectamente compatible con aplicaciones ASP.NET MVC5  Framework 4.5.

FormsAuthentication (Web.config)

En primer lugar crearemos el o los usuarios administradores de la aplicación. Este proceso lo realizamos en el elemento <authentication/>, dentro de la sección <system.web/> del archivo de configuración Web.config de la aplicación.

<authentication mode="Forms">
  <forms name=".ASPXAUTH" loginUrl="~/Login" timeout="10000">
    <credentials passwordFormat="SHA1">
      <user name="usuario" password="contraseña-encriptada" />
      <!-- Otros usuarios aquí... -->          
    </credentials>
  </forms>
</authentication>

mode="Forms": Habilita la autenticación mediante formularios.

name=".ASPXAUTH": Nombre de la Cookie que contendrá el Ticket de autenticación.

loginUrl="~/Login":  URL de redirección, en el caso de que ASP.NET no encuentre la Cookie de autenticación.

timeout="10000": Tiempo de expiración de la Cookie de autenticación expresado en minutos.

passwordFormat="SHA1": Algoritmo de encriptación de las contraseñas de usuario (SHA1 hash).

name="admin": Nombre del usuario.

password="contraseña-encriptada": Contraseña del usuario encriptada en formato SHA1 (más adelante veremos como realizar el proceso de encriptación).

El Controlador y las Vistas

A continuación crearemos la infraestructura necesaria para desarrollar el sistema de autenticación y administración de la aplicación.

Creamos un Controlador básico con las Acciones necesarias para el sistema de Login y las tareas administrativas (posteriormente implementaremos el código para cada una de estas Acciones).

    public class AdminController : Controller
    {
        // GET: Admin
        public ActionResult Index()
        {
            // Aquí acceso al módulo de administración...
            return View();
        }

        // GET: Admin/Login
        public ActionResult Login()
        {
            // Aquí acceso a la 'Vista' de Login...
            return View();
        }

        // POST: Admin/Login
        [HttpPost]
        public ActionResult Login(string usr, string pwd, string rme)
        {
            // Aquí lógica de autenticación...
            return View();
        }

        // GET: Admin/Logout
        public ActionResult Logout()
        {
            // Aquí lógica de desconexión...
            return View();
        }
    }

Creamos también las Vistas (por supuesto siempre dentro del directorio de referencia del Controlador, Views\Admin\). Para este ejemplo necesitaremos 2 Vistas: Login.cshtml e Index.cshtml.

Login.cshtml: Vista que contendrá el formulario de autenticación de usuarios. Este sería un ejemplo básico de Login mediante usuario y contraseña con la opción Remember me, basado en Bootstrap 3.2:

<div class="container-fluid">
    <div class="row">
        <div class="col-sm-6 col-md-4 col-md-offset-4">
            <div class="text-center">
                <div class="text-center">
                    <img class="img-thumbnail" src="~/Images/admin.png" width="128">
                </div>
                <br />
                @using (Html.BeginForm("Login","Admin"))
                {                
                    <input type="text" class="form-control" name="usr" 
                           placeholder="Usuario" required autofocus>
                    <br />
                    <input type="password" class="form-control" name="pwd" 
                           placeholder="Contraseña" required>
                    <br />
                    <button class="btn btn-lg btn-primary btn-block" type="submit">
                        Iniciar sesión
                    </button>
                    <label class="checkbox pull-left">
                        <input type="checkbox" name="rme" checked="">
                        <label>Remember me</label>
                    </label>                
                }
            </div>
        </div>
    </div>
</div>

Index.cshtml: Esta sería la Vista principal del módulo de administración. En un caso práctico podría ser un panel de control al estilo AdminLTE.

El código para la autenticación

Por último, solo quedaría codificar las Acciones del Controlador para dar la funcionalidad de autenticación al sistema de Login.

Esto lo haremos a través de la clase FormsAuthentication del espacio de nombres System.Web.Security. Esta clase proporciona una serie de métodos estáticos que interactuan con la configuración anteriormente establecida en el Web.config <authentication/>.

A continuación el código:

    public class AdminController : Controller
    {
        // GET: Admin
        public ActionResult Index()
        {
            if (this.Request.IsAuthenticated)
            {
                // Si el usuario está autenticado 
                // retorna la Vista de administración.
                return View();
            }
            else
            {
                // Si no, retorna la Vista de Login.
                return RedirectToAction("Login", "Admin");
            }
        }

        // GET: Admin/Login
        public ActionResult Login()
        {
            // Retorna la Vista de Login
            return View();
        }

        // POST: Admin/Login
        [HttpPost]
        public ActionResult Login(string usr, string pwd, string rme)
        {
            if (string.IsNullOrEmpty(usr) || string.IsNullOrEmpty(pwd))
            {
                return View();
            }
            else
            {
                bool _rememberMe = rme == "on" ? true : false;

                // Valida el usuario con los registrados en la
                // seccion <authentication/> del Web.config
                if (FormsAuthentication.Authenticate(usr, pwd))
                {
                    // Crea la Cookie con el Ticket de autenticación 
                    // para el usuario.
                    FormsAuthentication.SetAuthCookie(usr, _rememberMe);
                    // Redirige a la Accion 'Index'
                    return RedirectToAction("Index");
                }
                else
                {
                    // Si no es un usuario válido, retorna la
                    // Vista de Login.
                    return View();
                }
            }
        }

        // GET: Admin/Logout
        public ActionResult Logout()
        {
            // Elimina la Cookie con el Ticket de autenticación 
            // para el usuario.
            FormsAuthentication.SignOut();
            // Redirige a la Accion 'Index'
            return this.RedirectToAction("Index");
        }
    }

Encriptado de contraseña

Siempre es una buena práctica encriptar las contraseñas y datos sensibles de nuestra aplicación, a la hora de publicarla en un Web hosting externo a nuestra infraestructura corporativa. En el caso que nos ocupa, habíamos configurado la sección <authentication/> para utilizar contraseñas de usuario en formato SHA1 hash. Un ejemplo real con nombre de usuario 'admin' y contraseña 'admin', sería el siguiente:

<credentials passwordFormat="SHA1">
   <user name="admin" password="D033E22AE348AEB5660FC2140AEC35850C4DA997" />
</credentials>

Existen infinidad de páginas en Internet que generan online la cadena encriptada de caracteres SHA1 a partir de un texto dado, http://www.sha1-online.com es un ejemplo de estas.

Publicando nuestra aplicación (Web hosting)

En este punto ya hemos integrado el sistema de autenticación a nuestra aplicación ASP.NET MVC y todo funciona correctamente en nuestro servidor IIS local o de desarrollo. Es la hora entonces de publicar la aplicación en los servidores web de nuestro proveedor de hosting

Comenzamos a testear la aplicación, nos autenticamos en el sistema (Login), navegamos a través de las páginas que requieren usuarios autenticados, y en pocos minutos (o segundos) vemos como hemos perdido la autenticación y la aplicación nos redirige a la página de Login. Afortunadamente este es un problema muy común y de fácil solución.

El problema

Los proveedores de servicios de hosting funcionan con granjas de servidores web (Clustering) que implementan balanceo de carga para maximizar el rendimiento de las peticiones http y minimizar la carga de trabajo.

Cuando nos autenticamos en nuestra aplicación (Login), el servidor que atiende la petición crea un Ticket de autenticación que se almacena en una cookie que es enviada a nuestro navegador y nos permite navegar por las páginas que requieren usuarios autenticados.

Este Ticket de autenticación es creado a partir de que lo se llama un machineKey (clave de máquina). Cuando nuestra aplicación arranca por primera vez en un servidor web, un machineKey es creado automáticamente. Al autenticarnos, este machineKey es utilizado para firmar digitalmente el Ticket de autenticación. Por otra parte, cuando navegamos a una página que requiere usuarios autenticados, el servidor utiliza el machineKey para desencriptar el Ticket de autenticación de la Cookie para validar el usuario. 

El problema surge cuando es un servidor diferente (por el balanceo de carga) el que atiende la petición de autenticación, e intenta desencriptar el Ticket de autenticación con un machineKey diferente. El resultado de esta acción causaría la invalidación del Ticket de autenticación original (y de la Cookie), y la redirección del usuario a la página de Login.

La solución

ASP.NET nos permite definir un machineKey personalizado para cada una de nuestras aplicaciones web. Esto implica que los servidores donde se ejecute la aplicación, no auto-generen uno propio y utilicen el que la aplicación les propone.

La configuración del machineKey se realiza dentro de la sección <system.web/> del archivo de configuración Web.config de la aplicación:

<machineKey validationKey="B2A3A7ED71A85AC9BCBBBAD8407F9642EEFA3B17FD2583CFD66A3BAF407900D2C45A85379FA2B4BE2397BECF14650306B6292C96418B772055F01E1EE751E434"
decryptionKey="E60EC68AB3752322D236E5727370620398818355316FB5FDFBF00EE4344AE7B7"
validation="SHA1" decryption="AES" /> 

Es recomendable generar un machineKey aleatorio (y diferente) para cada una de la aplicaciones que queramos publicar en nuestro Web hosting. Desde este sitio web: ASP.Net MachineKey Generator podemos generar nuestros machineKey en función del Framework ASP.NET que estemos utilizando.

   EtiquetasASP.NET .NET MVC C#

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

Enviando ...

  Comentarios

No hay comentarios para este Post.



  Etiquetas

.NET Core .NET Framework .NET MVC .NET Standard AJAX ASP.NET ASP.NET Core ASP.NET MVC Bootstrap Buenas prácticas C# Cookies Entity Framework JavaScript jQuery JSON PDF Pruebas Unitarias Seguridad SEO Sql Server SqLite Swagger Validación Web API Web Forms

  Populares


  Nuevos















Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto