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.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
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.
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.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.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? 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
?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);
}