Search code examples
asp.netauthenticationwebformsforms-authentication

asp.net authentication of users best methods webforms


Whats the best way for authenticating web users and storing their details which i have a class for:

Should i use sessions or the forms auth cookie?

How can i access it from either way say like userclass.username?

I would like to store quite a lot of user information to stop database calls for stuff like: user type, user a full name, addresss, post code, foo1, foo2, foo3, foo4 and more. I know this could go in session or auth cookie user data. This question relates and links to https://stackoverflow.com/questions/18393122/whats-the-best-way-to-authenticate-a-user-and-store-user-details-sessions-or-fo which i have not had any help on

Really could do with some help and advice here as i have a few system which i need to do this for. Any comments appreciated.

thanks

************************************ Links *****************************

My code based roughly on:

http://www.shawnmclean.com/blog/2012/01/storing-strongly-typed-object-user-profile-data-in-asp-net-forms-authentication-cookie/

http://www.danharman.net/2011/07/07/storing-custom-data-in-forms-authentication-tickets/

************************************ EDIT *****************************

custom identity module

Public Module IdentityExtensions
Sub New()
End Sub

Private _CustomIdentityUser As CustomIdentityUser

<System.Runtime.CompilerServices.Extension> _
Public Function CustomIdentity(identity As System.Security.Principal.IIdentity) As CustomIdentityUser
    'If _CustomIdentityUser Is Nothing Then
    '_CustomIdentityUser = DirectCast(identity, CustomIdentityUser)
    _CustomIdentityUser = Nothing
    If identity.GetType = GetType(FormsIdentity) Then
        _CustomIdentityUser = New CustomIdentityUser(DirectCast(identity, FormsIdentity).Ticket)
    Else
        If identity.IsAuthenticated Then
            FormsAuthentication.RedirectToLoginPage()
        End If
    End If

    Return _CustomIdentityUser
End Function
End Module

My Custom identity user

Public Class CustomIdentityUser
Implements System.Security.Principal.IIdentity

Private ticket As System.Web.Security.FormsAuthenticationTicket
Private _Auth As Auth

Public Sub New(ticket As System.Web.Security.FormsAuthenticationTicket)
    Me.ticket = ticket
    _Auth = New projectabc.Auth(Me.ticket)
End Sub

Public ReadOnly Property Auth As Auth
    Get
        Return Me._Auth
    End Get
End Property
Public ReadOnly Property Username As String
    Get
        Return Auth.Username
    End Get
End Property

Public ReadOnly Property UserType As Enumerations.EnumUserType
    Get
        Return Auth.UserType
    End Get
End Property
Public ReadOnly Property OwnerType As Enumerations.EnumOwnerType
    Get
        Return Auth.OwnerType
    End Get
End Property


Public ReadOnly Property AuthenticationType As String Implements System.Security.Principal.IIdentity.AuthenticationType
    Get
        Return "Custom"
    End Get
End Property

Public ReadOnly Property IsAuthenticated As Boolean Implements System.Security.Principal.IIdentity.IsAuthenticated
    Get
        Return ticket IsNot Nothing
    End Get
End Property

Public ReadOnly Property Name As String Implements System.Security.Principal.IIdentity.Name
    Get
        Return Username
    End Get
End Property
End Class

Then as you can see the user class calls an auth class which basically has all the properties for the user and gets and set it etc.

Public Class Auth
Inherits BaseUser

Public Property _ticket As Web.Security.FormsAuthenticationTicket
Public RememberMe As Boolean

Private _IssueDate As DateTime?
Public ReadOnly Property IssueDate As DateTime?
    Get
        Return _IssueDate
    End Get
End Property
Private _Expired As Boolean
Public ReadOnly Property Expired As Boolean
    Get
        Return _Expired
    End Get
End Property
Private _Expiration As DateTime?
Public ReadOnly Property Expiration As DateTime?
    Get
        Return _Expiration
    End Get
End Property

Public Sub New(ticket As System.Web.Security.FormsAuthenticationTicket)
    Me._ticket = ticket
    Dim SignOutUser As Boolean = False
    Try
        If Not GetUserDetails() Then
            SignOutUser = True
        End If
    Catch ex As Exception
        SignOutUser = True
    End Try
    If SignOutUser Then
        HttpContext.Current.Response.Redirect("~/", True)
        SignOut()
    End If
End Sub

Public ReadOnly Property IsAuthenticated() As Boolean
    Get
        Return HttpContext.Current.User.Identity.IsAuthenticated
    End Get
End Property

Public Function SetAuthCookie() As Int16
    Dim encTicket As String
    Dim userData As String = CreateUserDataString()
    If userData.Length > 0 And userData.Length < 4000 Then
        Dim cookiex As HttpCookie = FormsAuthentication.GetAuthCookie(MyBase.Username, True)
        Dim ticketx As FormsAuthenticationTicket = FormsAuthentication.Decrypt(cookiex.Value)

        'Dim newTicket = New FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData, ticket.CookiePath)
        'encTicket = FormsAuthentication.Encrypt(newTicket)

        'Use existing cookie. Could create new one but would have to copy settings over...
        'cookie.Value = encTicket
        'cookie.Expires = newTicket.Expiration.AddHours(24)

        'HttpContext.Current.Response.Cookies.Add(cookie)
        Dim ticket As New FormsAuthenticationTicket(1, ticketx.Name, DateTime.Now, ticketx.Expiration, False, userData, ticketx.CookiePath)
        encTicket = FormsAuthentication.Encrypt(ticket)
        cookiex.Value = encTicket
        'Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, encTicket)

        HttpContext.Current.Response.Cookies.Add(cookiex)
    Else
        Throw New ArgumentOutOfRangeException("User data length exceeds maximum", New ArgumentOutOfRangeException)
    End If

    Return encTicket.Length
End Function

Public Function GetUserDetails() As Boolean
    Dim valid As Boolean = False      

    If _ticket IsNot Nothing Then
        With _ticket
            RememberMe = .IsPersistent
            Username = .Name
            _IssueDate = .IssueDate
            _Expired = .Expired
            _Expiration = .Expiration

            Try
                If .UserData.Length > 0 Then
                    valid = SetUserDataFromString(.UserData)
                Else
                    'we have a problem
                    Return False
                End If
            Catch ex As Exception
                'sign them out as they may have a cookie but the code may have changed so it errors thus make them login again.
                'SignOut()
                Throw ex

            End Try
        End With
    End If

    Return valid

End Function

Private Function CreateUserDataString() As String
    Dim sData As New System.Text.StringBuilder

    With sData
        .Append(MyBase.UserID)
        .Append("|") 'delimeter we are using
        .Append(Int16.Parse(MyBase.UserType))
        .Append("|")
        .Append(Int16.Parse(MyBase.Security))
        .Append("|") 'delimeter we are using
        .Append(MyBase.FirstName)
        .Append("|")
        .Append(MyBase.LastName) 
    .Append("|")
        .Append(MyBase.foo1)  
    .Append("|")
        .Append(MyBase.foo2) 
        .Append("|")            
        .Append(MyBase.foo3)  
    .Append("|")
        .Append(MyBase.foo4) 
    End With


    Return sData.ToString
End Function    

   Public Function SetUserDataFromString(userData As String) As Boolean
    Dim valid As Boolean = False
    Dim sData As New System.Text.StringBuilder
    'check we have a delimeter
    Dim arUserData As String() = userData.Split("|")
    Try


    If arUserData.Count >= 9 Then '9 because that the user only stuff
        With arUserData
            MyBase.UserID = arUserData(0)
            MyBase.UserType = arUserData(1)
            MyBase.Security = arUserData(2)
            MyBase.FirstName = arUserData(3)
            MyBase.LastName = arUserData(4)
            MyBase.foo1 = arUserData(5)
    MyBase.foo2 = arUserData(6)
    MyBase.foo3 = arUserData(7)
    MyBase.foo4 = arUserData(8)
        End With
        valid = True
    Else
        valid = False
        End If

    Catch ex As Exception
        Throw New ArgumentOutOfRangeException("User data length to short", New ArgumentOutOfRangeException)
    End Try
    Return valid
End Function

Public Sub SignOut()
    FormsAuthentication.SignOut()
End Sub

Solution

  • As you have posted the code may be you would get some good answer. I would try to answer as per my understanding, hope that helps.

    1. From where do you invoke CustomIdentity? In the first method in the Else part I think you might want to use Not IsAuthenticated if you want the user to redirect to the login page. Most of the times if the ticket is invalid you don't even have to do it the framework does it for you.
    2. In the CustomIdentityUser you have a private member _Auth which you never use while returning some values through the properties. You are using Auth.UserName instead of _Auth.UserName, I am not sure how that works unless the UserName is static member.
    3. Why is you custom Identity dependent on Auth? You can pass on the required data to the custom identity through contructor or by exposing public setters. Why do you need the auth ticket inside the identity?
    4. The Auth Class has the expiration date and other stuff which is not required. You can have a simple User class to store the basic details of the user. Why are you redirecting the user from the constructor of Auth?
    5. The GetUserDetails and SetUserDataFromString I do not know why do you need all these methods. Its just matter of Serializing and Deserializing the User class.

    I understand that you must have referred some blog out there to implement this authentication, but you have lot of scope to simplify this.

    Read this post. Specifically how the custom principal is implemented and how the authticket is set and the PostAuthenticateRequest method.

    Here is some sample code that might help

    interface ICustomPrincipal : IPrincipal
    {
        int UserId { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
    }
    
    public class CustomPrincipal : ICustomPrincipal
    {
        public CustomPrincipal()
        {
    
        }
    
        public CustomPrincipal(string userName)
        {
            Identity = new GenericIdentity(userName);
        }
    
        public int UserId
        {
            get;
            set;
        }
    
        public string FirstName
        {
            get;
            set;
        }
    
        public string LastName
        {
            get;
            set;
        }
    
        public IIdentity Identity
        {
            get;
            private set;
        }
    
        public bool IsInRole(string role)
        {
            return false;
        }
    
    }
    
    public class User
    {
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
    
    public static class FormsAuthHelper
    {
        public static void SetAuthTicket(User user, HttpContextBase context)
        {
            var serializer = new JavaScriptSerializer();
            var userData = serializer.Serialize(user);
            var authTicket = new FormsAuthenticationTicket(
                1, user.UserName,
                DateTime.Now, DateTime.Now.AddMinutes(30),
                false, userData);
            var ticket = FormsAuthentication.Encrypt(authTicket);
            var faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, ticket);
            context.Response.Cookies.Add(faCookie);
        }
    
        public static void Logout()
        {
            FormsAuthentication.SignOut();
            FormsAuthentication.RedirectToLoginPage();
        }
    
        public static CustomPrincipal GetPrincipal(User user)
        {
            return new CustomPrincipal(user.UserName) { FirstName = user.FirstName, LastName = user.LastName, UserId = user.EntityId };
        }
    }
    

    Post authenticate request event looks like this

     protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
     {
        var authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (authCookie == null || authCookie.Value == string.Empty)
           return;
    
        try
        {
           var ticket = FormsAuthentication.Decrypt(authCookie.Value);
           var serializer = new JavaScriptSerializer();
           var user = serializer.Deserialize<User>(ticket.UserData);
           var newUser = FormsAuthHelper.GetPrincipal(user);
    
           HttpContext.Current.User = newUser; 
        }
        catch
        {
                //do nothing
        }
     }
    

    And finally when the user logs in

    public ActionResult Login(LoginModel loginModel)
    {
       if (ModelState.IsValid)
       {
          var user = _userRepository.Get(x => x.UserName == loginModel.UserName).SingleOrDefault();
          if (user != null && PasswordHash.ValidatePassword(loginModel.Password, user.Password))
          {
             FormsAuthHelper.SetAuthTicket(user, HttpContext);
             return RedirectToAction("Index", "Home");
          }
          ModelState.AddModelError("NotFound", "User not found");
       }
       return View(loginModel);
    }