viernes, 5 de junio de 2015

Resolución de problemas con Forms Authentication

Herramientas usadas


  • LogParser 2.2
  • Fiddler
  • Microsoft Network Monitor 3.4

Resumen


A menudo, cuando usas Forms Authentication en una aplicación ASP.NET (no aplica para aplicaciones MVC), se da la necesidad de tener a mano una guía de resolución de problemas cuando un request sucede que intermitentemente la aplicación te redirige a la página login de ese mismo sitio. En un ambiente de Desarrollo puedes fácilmente debugear este problema atachando el proceso en el Visual Studio. En ambiente productivo, sin embargo, la tarea se vuelve muy complicada. Para tracear un problema como este, debes logear toda la información que puedas obtener relativa al problema, hasta llegar a la raíz misma de este.

En esta guía, mostraremos un resumen de lo que es el Forms Authentication. Veremos los escenarios en que un usuario puede ser redirigido a la página de login y como capturar datos que sean relevantes para aislar el problema. También veremos como implementar una interfaz IHttpModule para logear información relativa a Forms Authentication.

Resumen de la Autenticación de ASP.NET Forms


La autenticación de formulario permite autenticar un usuario usando tu propio código para mantener el token de autenticación ya sea en una cookie o en la url de la página. La autenticación participa en el ciclo de vida de una página ASP.NET a través de la clase FormsAuthenticationModule. Puedes acceder a la información de la autenticación a través de la clase FormsAuthentication.

Para usar una autenticación de formulario, creas una página login que colecciona credenciales del usuario y que incluyen código para autentificar dichas credenciales. Típicamente configuras la aplicación para que haga un redirect a una página login cuando los usuarios intentan acceder a un recurso protegido, como una página que requiere autenticación. Si las credenciales del usuario son válidas, puedes llamar a métodos de la clase FormsAuthentication para hacer un redirect de vuelta al request que originalmente se solicitó el recurso con el ticket apropiado de autenticación (cookie). Si no quieres hacer un redirect, puedes solo obtener la cookie de autenticación del formulario o setearla. En los request siguientes, el navegador del usuario pasa la cookie de autenticación en el mismo request, el cual hace que se salte la página de login.

Por defecto, la clase FormsAuthenticationModule se agrega al archivo Machine.config. La clase FormsAuthenticationModule administra el proceso de FormsAuthentication.

La siguiente es una entrada del archivo Machine.config:

<httpModule> 
     <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />           
</httpModule>";

Configuras la autenticación por formulario usando un elemento de configuración. En el caso más simple, tienes una página login. En el archivo de configuración, especificas la URL donde se redirigirán los request no autentificados. Entonces defines las credenciales válidas, en un archivo web.config o en un archivo separado. El siguiente ejemplo muestra una sección de un archivo de configuración que muestra una página login ylas credenciales de autenticación del método Authenticate. Los passwords han sido encriptados usando el método HashPasswordForStoringInConfigFile.

<authentication mode="Forms"> 
     <forms name="MyAuthCookie" loginUrl="/Login.aspx">     
       <credentials passwordFormat="SHA1">        
         <user name="Kim" password="07B7F3EE06F278DB966BE960E7CBBD103DF30CA6" />         
         <user name="John" password="BA56E5E0366D003E98EA1C7F04ABF8FCB3753889"/>         
       </credentials>
     </forms>
</authentication>

Luego de una autenticación ok, el módulo de FormsAuthenticationModule setea el valor de la propiedad User a una referencia del usuario autentifico. El siguiente código de ejemplo muestra como leer con programación la identidad del usuario autentificado con formularios.

String authUser2 = User.Identity.Name;

Una forma conveniente de trabajar con formularios de autenticación es usar controles de ASP.NET membership y ASP.NET login. ASP.NET membership te permite guardar y administrar información del usuario e incluir métodos para autentificar usuarios. Controles de ASP.NET login trabajan en conjunto con ASP.NET membership. Estos encapsulan la lógica para consultar las credenciales del usuario, validar al usuario, recobrar o reemplazar passwords, etc. De echo, los controles de ASP.NET membership y ASP.NET login proveen una capa de abstracción sobre la autenticación de formulario. Esta características reemplazan la mayoría de todos le típico trabajo que generalmente haces cuando tienes que usar autenticación de formularios.

Escenarios


Razones en la que un request se auto redirige a la página login.aspx:

La cookie de autenticación se pierde.

Escenario 1:

Un usuario se logea en el sitio web y en algún punto, el cliente envía un request al servidor y la clase FormsAuthenticationModule no recibe la cookie o no puede leerla.

Escenario 2:

La cookie de autenticación puede perderse cuando el navegador del cliente alcanza el límite de cookies creadas. Por ejemplo en Internet Explorer el límite es 20. Cuando ingresa la cookie número 21, el navegador elimina una cookie de la colección. Si se elimina la cookie .ASPXAUTH, el usuario será redirigido a la página de login en el siguiente request.

Escenario 3:

Luego que el request sale del cliente, hay varias capas que pueden afectar a los paquetes que están siendo enviados. Para determinar si algún dispositivo de red está eliminando la cookie, tienes que capturar una traza de red en el cliente y en el servidor, y entonces buscar en el cuerpo del request si está la cookie. Debes asegurarte que en el request del cliente se envío la cookie y chequear con una traza si se recibió ficha cookie.

Time-out en el ticket de autenticación de formulario

Una cosa que debes tener en mente en las aplicaciones hechas con ASP.NET 2.0 o superiores, es que el timeout de la autenticación de formulario cambió a 30 minutos por defecto. Esto significa que luego de 30 minutos de inactividad, el usuario será reenviado al login (nota: Cada vez que se accede al sitio, se resetea el reloj a 30 minutos - así se cuenta sólo cuando no se está haciendo nada).

Si quieres cambiar el valor del timeout a uno más largo, solo debes cambiar el valor en el web.config local (el valor del timeout está en minutos):

<system.web> 
     <authentication mode="Forms">   
       <forms timeout="50000000"/>                 
     </authentication>
</system.web>

Escenario 4:

La autenticación del formulario puede dar timeout antes que el valor seteado en el archivo de configuración.

Si el ticket de autenticación por formulario es generado manualmente por código, el timeout del ticket sobreescribe el valor seteado en el archivo de configuración. Por lo tanto, si ese valor es menor que el valor del archivo de configuración, el ticket del formulario de autenticación expirará antes y viceversa. Por ejemplo, asumamos que el atributo timeout está seteado a 30 (30 min) en tag Form del archivo Web.config y por código valor de Expiration del ticket está en 20. En este caso, el ticket del formulario de autenticación expiará pasados los 20 minutos y no a los 30 y el usuario tendrá que logearse de nuevo.
Event code: 4005
Event message: Forms authentication failed for the request. Reason: The ticket supplied has expired.

Escenario 5

Una aplicación web ASP.NET 4 usa autenticación de formulario y el visor de eventos dice:

Event code: 4005 
Event message: Forms authentication failed for the request. Reason: The ticket supplied was invalid.

Recopilación de datos y resolución de problemas

Resolución del problema 1:

Puedes determinar si un request no contiene una cookie activando el cookie logging de Microsoft Internet Information Services (IIS). Para hacer esto sigue estos pasos en IIS 6:

-Abre IIS Microsoft Management Console (MMC).
-Botón derecho en el sitio web y luego Properties.
-Botón en el tab Web Site y clic en Enable Logging.
-Asegúrate que el formato del log es W3C Extended Log File Format.
-Clic en Properties. -Clic en Advanced tab y clic en Extended Properties.
-Bajo las Extended Properties, selecciona el checkbox Cookie(cs(Cookie)) y Referer (cs(Referer)). En IIS 7 es similar: Cuando seleccionas el formato "W3C" del combo Format y en "Select Fields" selecciona los 2 checkbox de las cookies.

Luego que el problema ocurre, determina cual es el cliente tiene el problema dado su dirección IP. Filtra el log IIS, con la columna "client IP address".

Nota: Puedes usar Log Parser para formatear los logs del IIS. Baja Log Parser del sitio oficial de Microsoft: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=24659

Un request con cookies quedará registrado así:

2015-06-02 19:54:41 172.31.2.11 POST /Ventas/TestRequest - 80 - 172.31.17.26 Mozilla/5.0+(Windows+NT+6.1;+WOW64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/43.0.2357.81+Safari/537.36 Cookie1=Hola;+Cookie2=dyxj/ney47DaZiwEMxUJrw/RvTomKx;+Cookie3=NALDO; http://sitio.cl/ 200 0 0 378

Luego de que tengas la lista de request de un usuario específico, busca por los request de la página login. Ya sabes que hay un redirect a esta página, así que busca por los request que suceden antes de la redirección. Si ves que no hay cookie en dichos request, el cliente no la mandó o esta fue eliminada de la red entre el cliente y el servidor.

Nota: El primer request del usuario no tiene la cookie de autenticación a menos que crees un cookie persistente. El log del IIS sólo muestra las cookies que son recibidas en el request. El primer request que debería tener la cookie de autenticación será la request después que suceda se realice el login de forma correcta.

Resolución del problema 2:

Microsoft Internet Explorer tiene las siguientes limitaciones en cuanto a las cookies (limitaciones RFC 2109):
  • 300 cookies en total
  • 4096 bytes por cookie
  • 20 cookies por cada host o dominio

El artículo oficial es: http://support.microsoft.com/kb/306070

La cookie de autenticación puede perderse si se alcanzó el límite de cookies en el cliente. En Internet Explorer tiene la limitación de 20 cookies. Eso quiere decir que si llega una cookie 21, se sobreescribe la primera. Si la cookie .ASPXAUTH es eliminada, el usuario será redirigido a la página de login en el siguiente request. Puedes usar fiddler para ver los header de los request y response y así saber si el cliente está mandando la cookie o no. Puedes bajar Fiddler 2 de Telerik: http://fiddler2.com/fiddler2/

Ejecuta fiddler en el cliente, elimina las trazas http existentes, accede a tu aplicación que implementa la autenticación de formulario, logeate y observa el tráfico http en fiddler para ver si hay algún cambio de la cookie entre el cliente y el servidor. Luego que capturas el tráfico, si fue ok el login, haz doble clic al request para las respuestas, y en tab Cookie ver que se cree la cookie con Set-Cookie.

Por defecto Internet Explorer puede almacenar un máximo de 20 cookies por dominio. Si un servidor envía más de 20 cookies a un cliente, el navegador automáticamente elimina las cookies más viejas.

Cada cookie consiste en un par nombre-valor. Este par debe ser seguido por valores pares de atributos separados por punto y coma. Este límite ha sido aumentado para simplificar el desarrollo y hosting de aplicaciones web sobre dominios que deben usar cookies. Instalando el update 937143 incrementa el número de cookies que Internet Explorer puede almacenar por cada dominio de 20 a 50. Para más detalles mira este link: http://support.microsoft.com/kb/941495

Resolución del problema 3:

Luego que el request sale de un cliente, hay varias capas que pueden afectar a los paquetes que son enviados como un firewall, proxys o balanceadores de carga. Para determinar si un dispositivo de red está eliminando una cookie, tienes que capturar el tráfico de red entre el cliente y el servidor, y ver como está la cookie en el cuerpo del request. Tienes que ver el cliente y ver si la cookie se envió, y luego en el servidor ver la traza para asegurarte que llegó la cookie.

Request del cliente

Este es un request GET luego que el usuario ha sido autenticado. El ticket de autenticación está marcado en amarillo. Esto confirma que la cookie existe en el lado del cliente. Cuando usas una herramienta de captura de red, como Netmon, verás el tráfico que realmente es enviado a través del adaptador.


Request por el lado del servidor

Cuando ves que el request llegó al servidor, te debes asegurar que llegó la misma información que salió del cliente. Si el servidor no recibe la misma información, necesitas investigar otros dispositivos de la red para determinar donde se eliminó la cookie.

Nota: Ha habido casos de filtros ISAPI que eliminan las cookies. Si confirmas que el servidor web recibió la cookie, pero la cookie no se ve en los Logs del IIS, chequea los filtros ISAPI. Tienes que eliminar los filtros y validar que el problema se resolvió.

Resolución del problema 5:

-Si el escenario involucra una granja de servidores o web farm (un balanceador, distintos servidores, una base de datos, un worker process por cada sitio), los Machinekeys deberían ser los mismo en todos lados. Usa el mismo machinekey para mantener la consistencia entre todos los servidores de la granja:

<machineKey validationKey="87AC8F432C8DB844A4EFD024301AC..." decryptionKey="E001A307CCC8B1ADEA..." validation="SHA1" />

-Compara los valores de los timeout de ambos módulos de autenticación de formulario y el módulo sesión de todos los servidores.

-Compara la versión del archivo System.Web.dll dentro de la carpeta Framework de ASP.NET 4 entre todos los servidores de la granja. Cuando falla con el error: Forms authentication failed for the request. Reason: The ticket supplied was invalid. Esto pasa cuando alguno de los servidores no está en la misma versión de Update del MS .NET framework 4.

-Instala Reliability Update 1 para .NET Framework 4 kb2533523 en el servidor que falta y reiniciar ese servidor. Esto corregirá el problema: http://support.microsoft.com/kb/2533523

Fuente: http://www.iis.net/learn/troubleshoot/security-issues/troubleshooting-forms-authentication