Icono del sitio Programando a medianoche

Autenticación por formularios en ASP.NET

Prólogo e inicios

En algunas tecnologías web antiguas, como por ejemplo ASP (en el caso de Microsoft) la programación de la autenticación de los usuarios quedaba en manos de los programadores. A pesar de que no es una tarea extremadamente compleja, demandaba tiempo de desarrollo, pruebas y correcciones por cada sitio que se desarrollaba y aunque las técnicas utilizadas para esta funcionalidad se encontraban informalmente estandarizadas, siempre podían existir diferencias en las implementaciones (quien haya caído en la suerte de tener que modificar una aplicación legada, posiblemente sabrá a que nos referimos ), lo que influía en los tiempos y costos del desarrollo y si lo analizamos parecería tener poco sentido el gasto de esfuerzos de desarrollo en una tarea que es tan repetitiva. Quizás esa sea una de las causas por las que ASP.NET provee un sistema de autenticación ya incluido, lo que implica código, desarrollado, testeado, mantenido y listo para usar. En este artículo nos adentraremos en la autenticación por formularios desde sus comienzos en las primeras versiones de ASP.NET hasta la versión 3.5 (y diría que también hasta la versión 4.0, ya que hasta el momento no se han introducido modificaciones sobre estos ítems en la versión 4.0 y según parece, tampoco hay intenciones de hacerlo) y comentaremos algunos otros temas relacionados.

Para empezar diremos que la autenticación es el acto de confirmar que algo o alguien es quien dice ser y autorización es el acto de dar permisos (o denegárselos) a algo o alguien (ya autenticado) sobre un recurso, diremos además que algo o alguien es anónimo si no ha sido autenticado, que la acción de loguearse (login) consta de informar las credenciales a fin de poder autenticarse y la acción de desautenticarse (logout), si me permiten el término aunque no suene de lo mejor, consta de volver a un usuario autenticado al estado de anónimo.

En ASP.NET el modo de autenticación por formularios es aplicable para aplicaciones web que requieren autenticación, siempre que la misma no sea autenticación de Windows, para tales casos ASP.NET ofrecerá la opción de establecer el modo de autenticación Windows, pero ese es otro tema.

ASP.NET a través del modo de autenticación por formularios nos brindará mecanismos y funcionalidades que nos ayudarán a realizar ambas tareas: autenticación y autorización.

Para que el modo de autenticación por formularios funcione, solo debemos activarlo y la forma es simplemente ajustar la propiedad mode del tag authentication (del archivo web.config) en Forms, tal como se muestra a continuación:

<authentication mode="Forms">

El modo de autenticación por formularios creará una cookie con la información básica del usuario (obviamente encriptada), la cual será enviada por el browser en cada solicitud y el Servidor analizará la información de esta cookie para hacer los chequeos de autenticación y autorización necesarios. Este modo de funcionamiento es necesario debido a que en un entorno web las conexiones entre el cliente y el servidor se establecen y se cortan en cada solicitud. Por defecto la cookie tomará el nombre “.ASPXAUTH”.

En el siguiente esquema pueden observarse paso a paso las verificaciones de autenticación y autorización que realizará ASP.NET cuando una solicitud es procesada y se está utilizando autenticación por formularios:



El proceso graficado será realizado automáticamente por ASP.NET con simple hecho de haber establecido el atributo mode como Forms.
En nuestros ejemplos (que los estaremos realizando sobre Visual Studio 2008 SP1) utilizaremos una aplicación web que inicialmente contendrá la página Default.aspx, que es creada automáticamente junto con el proyecto.



Si en el ejemplo, luego de haber establecido el modo de autenticación por formularios, intentamos acceder a la página Default.aspx, notaremos que podremos hacerlo sin problemas, pero no debemos preocuparnos, este comportamiento se debe a que aún no hemos definido las reglas de acceso, por ahora cualquier usuario podrá acceder a cualquier recurso del sitio.

Para evitar que esto suceda, deberemos configurar las reglas de acceso. Por ahora, para simplificar lo haremos agregando las siguientes líneas dentro de la sección system.web del archivo web.config:

<authorization>
      <deny users="?"/>
</authorization>

Luego veremos en detalle las opciones disponibles para esta sección de la configuración, pero la sintaxis que utilizamos es bastante intuitiva, estaremos indicando que deberá negársele el acceso a cualquier recurso del sitio a los usuarios anónimos (identificados con el signo “?”, recordemos que un usuario anónimo es aquel que no se ha autenticado).

Si luego de esta modificación intentamos acceder nuevamente a Default.aspx (que es un recurso del sitio) veremos que somos automáticamente redireccionados a la página login.aspx, obteniendo un resultado similar al siguiente en la barra de direcciones del explorador:

http://localhost:6931/login.aspx?ReturnUrl….

Se obtendrá un error dado que la página login.aspx no existe, pero ese detalle mínimo pierde importancia ya que como podemos ver, el mecanismo está funcionando.

Lo que está haciendo ASP.NET es verificar la cookie, y al no encontrarla nos ha enviado a un formulario para que ingresemos nuestras credenciales y podamos autenticarnos.

La pagina login.aspx es la página de redirección que utiliza ASP.NET por defecto, pero es posible modificar este comportamiento definiendo un tag denominado forms dentro del tag authentication en el web.config como se muestra a continuación:

<authentication mode="Forms">
      <forms loginUrl="ingreso1.aspx"></forms>
</authentication>

Y no habrá quien se sorprenda de que luego de esta última modificación, la redirección sea a:

http://localhost:6931/ingreso1.aspx?ReturnUrl…….

Existen varias opciones aplicables al tag forms, algunas de ellas, además de loginUrl son:

Para continuar con el ejemplo agregaremos la página ingreso1.aspx que será desde ahora en más nuestra página donde el usuario deberá ingresar sus credenciales para autenticarse (login).

Con estas simples modificaciones en el archivo de configuración hemos conseguido dar un primer paso a incorporarle a nuestro sitio la autenticación por formularios. Ahora que hemos conseguido que nadie pueda ingresar, debemos permitir que algunos usuarios si puedan hacerlo, y en particular aquellos a los que deseemos permitirles el ingreso. En este punto encontraremos dos opciones, una de ellas es encargarnos de autenticar al usuario por nuestra cuenta, lo que implica hacer el chequeo manualmente vía código y la otra será una opción automática con algunas particularidades.

Para el primer caso tomará importancia una clase llamada FormsAuthentication. Esta clase (como su nombre indica) nos ayudará a realizar todas las funcionalidades relacionadas con la autenticación por formularios programáticamente, como por ejemplo autenticar a un usuario y redireccionarlo a la pagina que el mismo había solicitado cuando se le denegó el acceso.

Para realizar la autenticación vía código con la ayuda de la clase FormsAuthentication escribiremos lo siguiente:

protected void Login_Click(object sender, EventArgs e)
        {
            if (Usuario.Text == "Juan" && Clave.Text == "clavedejuan")
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
            {
                LabelError.Text = "Usuario o clave incorrecto";
            }
        }

Hemos asumido que poseemos los cuadros de texto, Usuario, Clave y el botón Login, donde hemos utilizado el evento Click del mismo para agregar el código que puede verse en las líneas superiores. De más está decir que no hemos mencionado a los validadores con el único objetivo de simplificar el ejemplo.

Si probamos el ejemplo intentando ingresar a la página Default.aspx seremos redireccionados a la página Ingreso1.aspx donde deberemos ingresar nuestras credenciales, tal como se muestra a continuación:


Solo podremos acceder a Default.aspx si utilizamos como usuario a “Juan” y como clave a “clavedejuan”.

En el código que terminamos de agregar podemos ver también que nos estamos valiendo del método RedirectFromLoginPage de la clase FormsAuthentication para indicar la autenticación del usuario y la redirección a la página solicitada originalmente por el mismo.

Por otra parte podríamos incluir en la página Default.aspx un botón con la siguiente funcionalidad:

protected void LogOut_Click(object sender, EventArgs e)
        {
            FormsAuthentication.SignOut();
            FormsAuthentication.RedirectToLoginPage();
        }

Al presionarlo se ha utilizado una vez más la clase FormsAuthentication para desautenticar al usuario y reenviarlo a la página de ingreso nuevamente.

Con estas operaciones hemos conseguido que un usuario pueda realizar las operaciones de autenticación, en tal caso será simple reemplazar la sentencia Usuario.Text == «Juan» && Clave.Text == «clavedejuan» que utilizamos en el ejemplo por la llamada a un método que acceda al repositorio que más deseemos para efectuar la validación de las credenciales del usuario.

Si se observa la clase FormsAuthentication se notará que existe un método llamado Authenticate que toma por parámetros un nombre de usuario y una clave y devuelve un valor booleano, podríamos preguntarnos si este método será capaz de realizar la autenticación por nosotros, esta es la segunda opción que habíamos mencionado en algunos párrafos previos y para probar como funciona, podremos modificar el código anterior de la siguiente forma:

protected void Login_Click(object sender, EventArgs e)
        {
            if (FormsAuthentication.Authenticate(Usuario.Text, Clave.Text))
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
                LabelError.Text = "Usuario o clave incorrecto";
        }

Si ahora intentamos ingresar nuevamente, ya no importa que usuario y clave utilicemos, no podremos hacerlo, lo cual es un resultado bastante esperable, si consideramos le hemos delegado el control de la autenticación ASP.NET pero aún no le hemos indicado quienes serán los usuarios válidos de la aplicación. Si se desea utilizar esta opción automática debe considerarse la particularidad de que los usuarios deberán definirse en el propio archivo de configuración web.config, como se muestra en el siguiente ejemplo:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx" >
    <credentials passwordFormat="Clear">
      <user name ="Pepe" password ="clavedepepe"/>
      <user name ="Pedro" password ="clavedepedro"/>
    </credentials>
  </forms>
</authentication>

Luego de agregar estos valores veremos que es posible ingresar con ambos usuarios. El atributo passwordFormat del tag credentials admitirá además de la opción Clear (claves en formato limpio, o sea en texto legible para quien abra el archivo web.config) las opciones MD5 y SHA1 que permitirán utilizar el hash de la clave del usuario utilizando los algoritmos MD5 o SHA1. Claro está que en tal caso deberemos crear un mecanismo capaz de establecer dichos valores, lo bueno es que para obtener los valores hash en ambos formatos la clase FormsAuthentication ofrecerá el método HashPasswordForStoringInConfigFile que permitirá tomar una clave limpia y obtener el valor de hash correspondiente en el formato deseado. En el ejemplo anterior, si deseamos utilizar por ejemplo MD5 deberemos efectuar las siguientes modificaciones:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx">
    <credentials passwordFormat="MD5">
      <user name="Pepe" password="957995AA67183A4D2A91F7DE3EB9A692"/>
      <user name="Pedro" password="B447CA7CBA96E91D68D43C5867522BF0"/>
    </credentials>
  </forms>
</authentication>

Donde los valores de hash se han obtenido efectuando las siguientes llamadas:

string HashedPasswordPepe = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedepepe " , "MD5");

y

string HashedPasswordJuan = FormsAuthentication.HashPasswordForStoringInConfigFile("clavedejuan " , "MD5");

Si bien todo esto funciona, creo que es inevitable pensar que se podría dar un próximo paso, ya que al fin de cuentas con este esquema que terminamos de ver, si bien ASP.NET y la clase FormsAuthentication nos han ayudado a controlar la autenticación y la autorización, la funcionalidad de la autenticación debimos codificarla, y cuando utilizamos la autenticación automática con el método Authenticate debimos definir a los usuarios en el archivo web.config. No sería nada malo que ASP.NET pudiese combinar ambas opciones y permitirnos efectuar la autenticación automáticamente sobre cualquier repositorio que deseáramos utilizar.

Evolución

Este modo de funcionamiento que hemos visto, propio de las versiones 1.x del Framework fue mejorado en la versión 2.0 donde se introdujeron cambios que permitieron automatizar ya no solo la autenticación contra cualquier repositorio, sino que también las operaciones (creación, modificación, eliminación, cambios de clave, etc.) de los usuarios involucrados en la misma, lo cual es bastante bueno ya que esas tareas son en realidad también bastante repetitivas.

Todos estos avances pudieron realizarse gracias al esquema de membrecía, más conocido como Membership (En el Framework 2.0 Se ha incluido además de la membrecía, la posibilidad de utilizar roles y perfiles de usuario).

La membrecía además de ofrecer código desarrollado, testeado y mantenido para el manejo de usuarios, brinda la posibilidad de utilizar controles ya incluidos en ASP.NET para realizar una variada cantidad de operaciones y la posibilidad de utilizar como repositorios un motor de base de datos SQL Server, Active Directory o un repositorio personalizado (donde deberá obviamente incluirse el código necesario para su funcionamiento). El siguiente esquema muestra a los actores involucrados y como los mismos se relacionan.


En el esquema puede observarse la existencia de los niveles: repositorios, proveedores, API de membrecía y controles incluidos en ASP.NET relacionados con la membrecía.

El primer nivel (repositorios) hace referencia al lugar donde la información de membrecía será almacenada. El segundo nivel (proveedores) está compuesto por clases dentro del Framework con la capacidad de establecer un enlace entre los repositorios del nivel inferior y la API del nivel superior.

El tercer nivel estará compuesto por la API de membrecía y el último nivel constituido por controles ya existentes en ASP.NET capaces de realizar las operaciones de membrecía y autenticación más frecuentes.
Bajo este esquema de funcionamiento la API en el tercer nivel podrá utilizar cualquier proveedor sin importar su implementación ya que todos los proveedores heredarán una clase en común llamada ProviderBase, brindándole a la API los miembros necesarios para que la misma pueda operar unívocamente con cualquiera de ellos.

Para el caso de SQL Server y Active Directory las implementaciones ya se encontrarán desarrolladas mientras que para realizar otras implementaciones será necesario crear una clase que herede de ProviderBase y se deberán sobreescribir los métodos necesarios apropiadamente.

En nuestro ejemplo utilizaremos como repositorio un motor SQL Server 2008 Enterprise Edition y como la implementación del proveedor SQL Server ya se encuentra desarrollada, simplemente deberemos preparar el repositorio y establecer el proveedor para que la API de membrecía lo utilice.

Antes que nada limpiaremos las líneas que habíamos agregado en el archivo web.config y dejaremos solo las siguientes:

<authentication mode="Forms">
  <forms loginUrl="ingreso1.aspx"/>
</authentication>
<authorization>
  <deny users="?"/>
</authorization>

El primer paso, como mencionamos previamente es el de preparar el repositorio, y considerando que estamos utilizando la opción de SQL Server, esto significará crear la base de datos, tablas, procedimientos almacenados, funciones, etc. para cada uno de los servicios que utilizararemos, para ser consistentes con la terminología utilizada, denominaremos a la membrecía como un servicio que instalaremos sobre el repositorio, (aunque en nuestro caso serán un conjunto de componentes de bases de datos). Además del servicio de membrecía existen otros servicios que también pueden instalarse, como indicaremos a continuación.

Un detalle es que en el caso de SQL Server Express 2005, el proveedor (SqlMembershipProvider) será capaz de instalar el servicio de membrecía automáticamente, pero en nuestro caso deberemos efectuar la tarea manualmente, aunque no es muy desalentador ya que la misma podrá efectuarse ejecutando por línea de comandos un comando llamado aspnet_regsql ajustando sus parámetros según nuestras necesidades. A continuación se describen los parámetros disponibles:

Para nuestro ejemplo ejecutaremos el comando de la siguiente forma:


Se ha utilizado la dirección:
<unidad:>\WINDOWS\Microsoft.NET\Framework\v2.0.50727

Ya que la versión 3.0 y 3.5 son en realidad upgrades de la versión 2.0 por lo que el archivo aspnet_regsql no se encontrará en carpetas de versiones posteriores, debido a que no ha sido modificado desde la versión 2.0.

Para la utilización de membrecía será suficiente definir el parámetro –A con el valor “m”, esto indicaría que solo instalaremos los servicios de membrecía. Hemos incluido también los servicios de roles (letra “r”), como comentamos previamente, por que haremos unas referencias adicionales en el artículo.

Si luego ingresamos a nuestro servidor de base de datos veremos que el comando ha creado la base de datos pruebamembership y la estructura que el proveedor SqlMembershipProvider utilizará.
Si el archivo aspnet_regsql es ejecutado sin parámetros, se mostrará un asistente (Wizard) que permitirá configurar los parámetros que hemos visto previamente en una ventana visualmente.

Con el repositorio construido, el segundo paso será configurar a ASP.NET para que utilice el repositorio, esto implica hacer que ASP.NET utilice el proveedor SqlMembershipProvider con la configuración de acceso a la base de datos que hemos creado. Este paso es bastante simple y consta de crear una cadena de conexión y configurar el proveedor. Por simplicidad, he definido la cadena de conexión desde las propiedades del proyecto de la siguiente forma:



Luego de haberse definido la cadena de conexión deberá configurarse el proveedor para que utilice esa cadena de conexión. Esta tarea se realizará dentro del mismo archivo web.config y constará en definir los tags membership y provider (dentro de system.web).

En nuestro caso utilizaremos la siguiente configuración:

<membership defaultProvider="PruebamembershipProvider">
  <providers>
    <add name="PruebamembershipProvider"  connectionStringName="FAYMS.Properties.Settings.pruebamembership"
        applicationName=" Pruebamembership 1"
        enablePasswordRetrieval="false"
        enablePasswordReset="true"
        requiresQuestionAndAnswer="false"
        requiresUniqueEmail="false"
        passwordFormat="Hashed"
        minRequiredPasswordLength="4"
        minRequiredNonalphanumericCharacters="0"
        type="System.Web.Security.SqlMembershipProvider" />
   </providers>
</membership>

En estas líneas hemos agregado a la membrecía el proveedor que hemos llamado PruebamembershipProvider que es del tipo SqlMembershipProvider y utiliza la conexión FAYMS.Properties.Settings.pruebamembership (que es la conexión que hemos definido desde las propiedades del proyecto previamente).

Para cada proveedor existirá además de los dos parámetros que hemos comentado una gran variedad de parámetros de configuración, que determinarán el comportamiento del mismo, para quien esté interesado en conocer todas las posibilidades, les dejo el siguiente enlace:

Propiedades de SqlMembershipProvider

Una vez realizados estos pasos la membrecía ha quedado terminada y lista para su uso.

Visual Studio ofrecerá incluso una herramienta denominada Web Site Administration Tool o simplemente WAT capaz entre otras cosas de operar con proveedores de membrecía. Si deseamos por ejemplo crear un usuario sobre el proveedor que hemos definido recientemente (PruebamembershipProvider) simplemente deberemos seleccionar la opción ASP.NET Configuration del Menú Project y aparecerá la aplicación WAT como se muestra a continuación:


Si seleccionamos la opción Security


Podremos por ejemplo crear un nuevo usuario seleccionando la opción Create user, donde un formulario de alta de usuarios se mostrará en pantalla, el comportamiento del mismo dependerá de las opciones parametrizadas en el proveedor. En nuestro caso según los parámetros configurados tomará el siguiente formato:


El botón Create User Creará el usuario en el repositorio, como puede verse a continuación:


Si dentro de la WAT cliqueamos la solapa Provider y la opción Select a different provider for each feature (advanced) veremos que se encuentra el proveedor que hemos definido junto al proveedor por defecto.



Nuestro proveedor ha quedado seleccionado ya que de esa forma lo hemos definido en el atributo defaultProvider del tag membership, en cuanto al proveedor ya incluido en ASP.NET que puede verse en la imagen, que no utilizaremos en nuestro ejemplo, podremos removerlo de la siguiente forma:
<membership defaultProvider="PruebamembershipProvider">
  <providers>
    <remove name="AspNetSqlMembershipProvider" />
    <add name="PruebamembershipProvider"
    ……..

Luego de tanto trabajo, aunque sin una sola línea de código, lo mínimo que esperaríamos es poder autenticar a Usuario1 tal como lo habíamos hecho hace unos cuantos párrafos atrás sin el uso de membrecía.
Si en la antigua pantalla de Ingreso1.aspx modificamos el código de siguiente forma:

protected void Login_Click(object sender, EventArgs e)
        {
            if (Membership.ValidateUser(Usuario.Text, Clave.Text))
                FormsAuthentication.RedirectFromLoginPage(Usuario.Text, false);
            else
                LabelError.Text = "Usuario o clave incorrecto";
        }

Y luego intentamos ingresar, veremos que todo funcionará según lo esperado. Como podemos ver, la validación del usuario ahora se efectuará mediante la API de membrecía, mientras que el manejo de la autenticación seguirá bajo el control de la clase FormsAuthentication.

La API de membrecía ofrecerá un amplio conjunto de métodos, además de ValidateUser se encontrarán CreateUser, DeleteUser, FindUsersByEmail, FindUsersByName, GeneratePassword, GetAllUsers, GetNumberOfUsersOnline, GetUser, GetUserNameByEmail y UpdateUser, donde sus nombres definen claramente su funcionalidad. La API de membrecía también ofrecerá unas cuantas propiedades y el evento ValidatingPassword que se disparará cuando un usuario es creado o su clave es modificada o reestablecida (reset).

Un objeto del tipo MembershipUser será devuelto por el método GetUser y mientras que un MembershipUserCollection será devuelto en los métodos FindUsersByEmail, FindUsersByName y GetAllUsers. La clase MembershipUser permitirá obtener información del usuario que representa (como por ejemplo: Nombre de usuario, Email, etc.) a través de sus propiedades. MembershipUser también contendrá métodos para realizar algunas operaciones como por ejemplo restablecer la clave de un usuario (reset) o modificarla.

Vuelvo a insistir en el hecho de que una ventaja muy interesante de la implementación de membrecía es que el uso de herencia y configuraciones en el web.config ha permitido un alto nivel de abstracción, el método ValidateUser de la clase Membership desconoce sobre que repositorio se está trabajando. Este desacople entre la API de membrecía y el manejo de los repositorios ha dado lugar a la creación de controles capaces de efectuar operaciones rutinarias de membrecía y autenticación en forma automática. Todo el esquema de autenticación que habíamos organizado en la página Ingreso1.aspx podrá reemplazarse con un solo control incluido ASP.NET y llamado adecuadamente Login.

Si reemplazamos los controles que habíamos agregado en la página Ingreso1.aspx por lo siguiente:

<asp:Login 
        runat="server" 
        ID="Login1" 
        RememberMeText="Recordar" 
        UserNameLabelText="Usuario" 
        PasswordLabelText="Clave"   
        TitleText="Ingreso a la aplicación" 
        FailureText="Usuario o clave incorrecto"
        LoginButtonText="Ingresar" 
        DisplayRememberMe="true">
</asp:Login>

Obtendremos el mismo resultado con la ventaja de validadores incorporados, la opción de recordar al usuario incluida, código testeado, mantenido y listo para usar. Otros controles incorporados son LoginStatus, LoginView, PasswordRecovery, ChangePassword y el interesante CreateUserWizard cuyos nombres nos dan una idea inequívoca de sus funcionalidades.

Ahora que hemos conseguido hacer andar a la autenticación, posiblemente será un buen momento para retomar un detalle que habíamos dejado sujeto con alfileres, el cual es el tag authorization. En su momento lo definimos de la siguiente forma:

<authorization>
  <deny users="?"/>
</authorization>

Donde indicábamos que los usuarios anónimos debían denegarse. El formato general del tag es el siguiente:

<authorization>
  <[allow|deny] users roles verbs />
</authorization>

Donde los tags allow o deny representarán reglas de acceso( a través de las operaciones de “permitir” o “denegar”) a los recursos del sitio. El atributo users representará a un grupo de usuarios y aceptará cuentas de usuario tanto como los simbolos “?” y “*” representando el símbolo “?” a los usuarios anónimos (no logueados) y “*” a todos los usuarios, de tal forma al especificar:

<deny users="?"/>

Se estaba indicando que se denegara el acceso a los recursos del sitio a usuarios anónimos, bajo estas circunstancias, cuando el browser intentase acceder a la página Default.aspx que es específicamente un recurso dentro de la carpeta principal del sitio, con un usuario aún no autenticado, el acceso sería denegado.

Es claro que en ningún lado especificamos que si debe permitirse el acceso a usuarios autenticados, la forma de especificarlo explícitamente sería la siguiente:

<authorization>
  <deny users="?"/>
  <allow users="*"/>
</authorization>

Esto implica en primer lugar que es posible utilizar varios tag allow o deny como reglas de acceso, en segundo lugar que son procesados en orden, o sea la regla deny se aplica primero, y para quienes hayan superado la regla luego se aplicará la regla allow (si invirtiéramos las reglas veríamos que cualquier usuario podría ingresar al sitio dejando a la regla deny posterior sin sentido) y en tercero y último lugar, esto implica que existe un allow * implícito (solo es necesario acceder a

<unidad:>\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\web.config

para confirmarlo, comentario aparte, en este archivo se movieron algunas cuantas definiciones existentes en el archivo machine.config de las versiones 1.x), por ese motivo si eliminamos el tag authorization cualquier usuario podrá ingresar y también podremos omitir el tag allow que escribimos explícitamente en el último ejemplo.
De forma similar podríamos haber utilizado la regla:

<authorization>
  <allow users="Usuario1"/>
  <deny users="*"/>
</authorization>

Si agregamos nuevos usuarios, además de Usuario1 que ya existía previamente e intentamos ingresar a Default.aspx veremos que solo Usuario1 puede hacerlo, los intentos con otros usuarios fallarán, y esto es lo que hemos especificado en las reglas de autorización, primero permitir el acceso al recurso pedido para Usuario1, y de quienes no cumplan la regla primera, ejecutar la segunda regla, que indica que para todos ellos, negar el acceso al recurso. Es posible incluir a varios usuarios separados por coma en el atributo users y evitarnos escribir unos cuantos tags.

Las reglas de autorización regirán para la carpeta del sitio tanto como para las subcarpetas del mismo, supongamos por ejemplo que nuestro sitio posee una subcarpeta de imágenes como se muestra en el ejemplo:



Y en nuestra página de login, Ingreso1.aspx agregamos lo siguiente:
<asp:Image runat="server" ImageUrl="Images/Logo.png" />

Si intentamos navegar la página Default.aspx seremos redireccionados a la página de login (esto se ha vuelto tan repetitivo que ya no debería sorprendernos) pero lo interesante es que veremos que la imagen aparece como inexistente en el browser aunque es claro que si existe.


Lo que sucede es que la imagen es también un recurso del sitio y la subcarpeta Images ha heredado las reglas de la carpeta padre, donde se establecía denegar el acceso a los usuarios no autenticados. Quien lo dude podrá simplemente probar autenticarse y luego volver a la página Ingreso1.aspx (sin desautenticarse) y la imagen aparecerá.
Es posible modificar las propiedades de las subcarpetas utilizando un tag llamado location, para nuestro ejemplo deberíamos escribir lo siguiente:
<location path="Images">
  <system.web>
    <authorization>
      <allow users="*"/>
    </authorization>
  </system.web>
</location>

El tag Location debemos definirlo dentro del tag Configuration y al mismo nivel del tag system.web del sitio y el mismo permitirá sobreescribir cualquier definición. En particular, para nuestro caso hemos sobreescrito la definición de autorización para que todo usuario pueda acceder a los recursos de la carpeta Images.

Además del atributo Users existe otro atributo llamado Verb en el tag authorization, que es opcional y permitirá especificar si la regla se aplicará a alguna acción de las siguientes GET, HEAD y POST. Por defecto el valor es “*” (para las tres acciones).

Finalmente veremos que existe la posibilidad de utilizar un atributo llamado roles (no es posible utilizar los atributos roles y users simultáneamente), la forma de uso es equivalente a los usuarios pero viendo esto podríamos preguntarnos ¿qué es un rol?.

Previamente habíamos comentado que el framework ofrecía además la posibilidad de incluir roles para los usuarios, la cual será seguramente una opción muy usual en escenarios de mundo real, comentaremos brevemente como utilizar roles con autenticación por formularios.

Para incluir roles en el esquema que hemos visto se ha adoptado una filosofía similar a la membrecía. Existirá en lugar de una API de membrecía una API de roles y un proveedor de roles, para nuestro caso, donde el repositorio era un servidor SQL Server 2008 Enterprise edition, en la creación del mismo, con la ayuda del comando aspnet_regsql hemos incluido los servicios de roles (incluyendo en el parámetro “-A” el valor “r”), así que solamente deberemos definir el proveedor de roles en el archivo web.config de la siguiente forma:

<roleManager enabled="true" defaultProvider="PruebaRolProvider">
  <providers>
    <remove name="AspNetSqlRoleProvider" />
    <add connectionStringName="FAYMS.Properties.Settings.pruebamembership"
          applicationName="PruebaRol1" name="PruebaRolProvider" type="System.Web.Security.SqlRoleProvider" />
  </providers>
</roleManager>

Desde la WAT encontraremos una sección para el manejo de roles e inclusión de usuarios en los mismos. La API de roles por otra parte nos permitirá realizar varias tareas programáticamente. La ventaja de los roles radica en que se simplificarán las tareas de autorización y configuración de permisos, al igual que sucede en el propio sistema operativo.

Después de tanto visto, habrá seguramente quien se haya percatado de un detalle incómodo, ya que por lo visto hasta ahora hay ciertos datos del usuario (como por ejemplo el mail del mismo) que se encuentran incluidos en este esquema, pero es de esperarse que en cualquier escenario real puedan existir otros datos no hayan sido contemplados, y esta situación es inevitable ya que las posibilidades parecen infinitas. Por ejemplo, en algún tipo de aplicación podría requerirse almacenar el peso o la altura del usuario (para dar un ejemplo simple). Los perfiles o Profiles permitirán incluir información personalizada de los usuarios de la aplicación y una característica interesante es que este servicio no está ligado solamente a la autenticación por formularios, aunque como es de esperarse, funcionará perfectamente con ella. Los perfiles son tema bastante extenso del cual espero, podamos escribir algún artículo posteriormente, pero quería al menos comentar su existencia para que quién los necesite pueda desde este punto continuar investigando de qué se trata el tema.

AJAX

Hay un último detalle que no quería dejar fuera del articulo y está relacionado con el uso de javascript, AJAX y autenticación por formularios, ya que imagino que habrá quien esté interesado en realizar la autenticación que hemos visto utilizando este esquema.

Si bien utilizar AJAX para realizar esta tarea no es complejo, lo que es aún mejor es que esta posibilidad ya ha sido considerada bajo lo que se ha denominado como Servicio de autenticación AJAX de ASP.NET (o ASP.NET AJAX Authentication Service).

Para utilizar el servicio de autenticación AJAX deberemos en primera instancia activar y configurar el servicio web de autenticación y luego utilizarlo desde el cliente mediante javascript y las librerías de AJAX de ASP.NET.

Para establecer el servicio web deberemos simplemente agregar en el archivo de configuración web.config (al mismo nivel del tag system.web) lo siguiente:

<system.web.extensions>
  <scripting>
    <webServices>
      <authenticationService enabled="true" requireSSL ="false" />
    </webServices>
  </scripting>
</system.web.extensions>

Luego utilizaremos las librerías de AJAX desde javascript para acceder al servicio web, para lo que deberemos incluir el control ScriptManager en cada página que deba acceder al servicio de autenticación desde javascript (de más está decir que existen formas de evitar el tener que agregar un control ScriptManager en cada página que deba acceder al servicio, pero lo explicaremos de esta forma para mantener el ejemplo lo más simple posible).

Nuestra intención en este punto será crear las versiones AJAX de autenticación por formularios para la autenticación (login) y desautenticacion (logout) tal como lo habíamos hecho previamente desde el servidor utilizando las clases Membership y FormsAuthentication.

Como comentamos previamente ASP.NET nos brindará una serie de funciones javascript que nos ayudarán a realizar estas tareas, para el caso del servicio de autenticación AJAX utilizaremos el namespace Sys.Services.AuthenticationService, donde encontraremos entre otras cosas, una función llamada login y otra llamada logout.

Crearemos nuevamente los cuadros de texto para el ingreso de las credenciales del usuario y un botón “Ingresar” de la siguiente forma:

<body>
  <form ….>
    <asp:ScriptManager ID="ScriptManager1" runat="server"/>
    <table>
        …..
                Usuario:
                <asp:TextBox runat="server" ID="txtUsuario" />
 	….		
                Clave:
                <asp:TextBox runat="server" ID="txtClave" TextMode="Password" />
 	….	
                <input type="button" onclick="Autenticar();" value="Aceptar" />
 	….
    </table>

Y luego definimos el siguiente código javascript:

function FuncLoginCompleted(EsAutenticado, Contexto, Metodo) {
            if (EsAutenticado) {                Sys.Services.AuthenticationService.get_defaultUserContext();
            }
            else {
                alert("Usuario o clave incorrecta");
            }
        }
        function FuncFailed(Error, Contexto, Metodo) {
            alert("Se ha producido un error: ");
        }
        function Autenticar() {
            Sys.Services.AuthenticationService.login($get("txtUsuario").value, $get("txtClave").value, false, null, "Default.aspx", FuncLoginCompleted, FuncFailed, null);
        }

Veremos que la autenticación funciona correctamente. Si observamos la función login notaremos que posee una amplia variedad de parámetros tales como:

Por otra parte la función loginCompletedCallback invocada poseerá los siguientes parámetros:

Mientras que la función failedCallback invocada poseerá los parámetros:

Con el ingreso (login) funcionando correctamente podremos redefinir la versión AJAX desautenticacion (logout) de la siguiente manera:

<body>
    <form ….>
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <input type="button" onclick="Salir();" value="Salir" />
    </form>
</body>

Con el siguiente código javascript:

function Salir() {
            Sys.Services.AuthenticationService.logout(null, null, FuncFailed, null);
        }
        function FuncFailed(error, Contexto, Metodo) {
            alert("Se ha producido un error");
        }

La función logout tomará los siguientes parámetros:

Y Finalmente la función logoutCompletedCallback invocada poseerá los siguientes parámetros:

Todas estas funciones forman parte de la extensa librería AJAX de ASP.NET y es posible encontrar toda la documentación de la misma en la dirección:

Librería de Microsoft AJAX

De todo lo visto nos queda como conclusión, que la autenticación por formularios conjuntamente con el uso de membrecía nos permitirá crear código claro, rápido, seguro y configurable para realizar las tareas de autenticación y manejo de usuarios, brindándonos también la posibilidad de incluir roles y e información personalizada en este esquema.

Con este último comentario le doy fin al artículo esperando (después de tanto leído) que lo visto sea de utilidad.

Salir de la versión móvil