ASP MVC: autenticación por formularios

Este año pasado he dado mis primeros pasitos con ASP MVC, siendo también mi primera incursión en programación web. Lo primero que a lo que tuve que enfrentarme (tras pasar por varios tutoriales y leerme un par de libros) fue la gestión del usuario. La aplicación MVC compartía componentes con otras (WCF y Windows Forms) y la gestión del usuario y sus permisos se hacía ya con una implementación de IPrincipal. Una búsqueda por Internet me llevó a que debía usar para esto la vieja autenticación por formularios.

Para ello, primero debía crear mi nuevo proyecto MVC sin autenticación. Creé, a continuación, otro con autenticación para coger todo lo que pudiera del código y ahorrarme trabajo: casi todo el diseño e la vista de login, la vista parcial de _LoginPartial, su implementación en la cabecera… Todo lo que me pudiera suponer un ahorro de trabajo.

Lo siguiente fue crearme la clase que iba a utilizar en la cookie de autenticación:

Public Class UsuarioActivoSerializeModel

    Public Property Id As Integer
    Public Property IdSede As Integer
    Public Property Permisos As Long
    Public Property Name As String

End Class

Tuve que decirle en el Web.config que iba a utilizar autenticación de formulario:

  <system.web>
(...)
    <authentication mode="Forms">
      <forms loginUrl="~/Login/Login" timeout="2880" />
    </authentication>
  </system.web>

Crear la cookie en al loguear al usuario:

<HttpPost>
<AllowAnonymous>
<ValidateAntiForgeryToken>
Public Function Login(model As LoginViewModel, returnUrl As String) As ActionResult
    If Not ModelState.IsValid Then
        Return View(model)
    End If

    ' Validamos usuario 
    AplicarConfiguracion()
    Dim mResult = GestorSGA.SetUsuario(model.Login, model.Password)
    If mResult.Id = 0 Then
        ' Login correcto
        System.Web.HttpContext.Current.User = Threading.Thread.CurrentPrincipal
        Dim mUsuarioActivo = TryCast(Threading.Thread.CurrentPrincipal.Identity, IUsuarioActivo)
            Dim mSerializeUsuario As New UsuarioActivoSerializeModel With {.Id = mUsuarioActivo.Id,
                                                                           .IdSede = mUsuarioActivo.IdSede,
                                                                           .Name = mUsuarioActivo.Name,
                                                                           .Permisos = mUsuarioActivo.Permisos}

                Dim mSerializer As New JavaScriptSerializer
                Dim mUserData As String
                mUserData = mSerializer.Serialize(mSerializeUsuario)
                Dim mAuthTicket As New FormsAuthenticationTicket(1, mUsuarioActivo.Login, DateTime.Now, DateTime.Now.AddHours(8), False, mUserData)
                Dim mEncryptedTicket As String = FormsAuthentication.Encrypt(mAuthTicket)
                Dim mCookie As New HttpCookie(FormsAuthentication.FormsCookieName, mEncryptedTicket)
                Response.Cookies.Add(mCookie)
                Return RedirectToLocal(returnUrl)
            Else
                ModelState.AddModelError("", "Intento de inicio de sesión no válido.")
                Return View(model)
            End If
        End Function

Y leerla en el evento PostAuthenticateRequest de la aplicación, donde descubrí que el HttpContext.Current.User y el Threading.Thread.CurrentPrincipal no eran la misma cosa y necesitaba que lo fueran, el primero para utilizarlo en la capa MVC y el segundo, en la lógica de negocio heredada. Nada grave. Agrego la gestión del evento en el Global.asax:

Private Sub MvcApplication_PostAuthenticateRequest(sender As Object, e As EventArgs) Handles Me.PostAuthenticateRequest
    Dim mCookie As HttpCookie = Request.Cookies(FormsAuthentication.FormsCookieName)
    If mCookie IsNot Nothing Then
        Dim mAuthTicket = FormsAuthentication.Decrypt(mCookie.Value)
        Dim mSerializer As New JavaScriptSerializer
        Dim mSerializeUsuario As UsuarioActivoSerializeModel = mSerializer.Deserialize(Of UsuarioActivoSerializeModel)(mAuthTicket.UserData)
        Dim mUsuarioActivo As New UsuarioActivo(mSerializeUsuario.Id,
                                                mSerializeUsuario.IdSede,
                                                mSerializeUsuario.Name,
                                                CType(mSerializeUsuario.Permisos, EPermisos),
                                                True)
        Dim mPrincipal As New SGAPrincipal(mUsuarioActivo)
        HttpContext.Current.User = mPrincipal
        Threading.Thread.CurrentPrincipal = HttpContext.Current.User
    End If
End Sub

Lo siguiente es impedir el acceso a los usuarios no identificados, salvo en las acciones marcadas con <AllowAnonymous>. Para esto, agregamos a nuestro FilterConfig.vb:

Public Module FilterConfig
    Public Sub RegisterGlobalFilters(ByVal filters As GlobalFilterCollection)
        filters.Add(New HandleErrorAttribute())
        ' <AllowAnonymous> lo tenemos que añadir al login y 
        ' a los puntos donde podamos acceder sin estar logueado.
        filters.Add(New AuthorizeAttribute())
    End Sub
End Module

Y, claro, el logoff:

<HttpPost>
<ValidateAntiForgeryToken>
Public Function LogOff() As ActionResult
    FormsAuthentication.SignOut()
    Session.Abandon()
    Return RedirectToAction("Index", "Home")
End Function

Por último, quedaba implementar mi propia clase derivada de AuthorizeAttribute, pero de eso ya hablo otro día.

Deja un comentario