I've got a web site that implements its own Forms based login, and creates an authentication cookie like this:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userID, DateTime.UtcNow, expiration, isPersistent, userFunctions);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
cookie.Expires = expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
The variable "userFunctions" contains a comma-separated list of roles that the user is a member of.
In my Global.asax file I'm retrieving those user functions in the following way:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = id.Ticket.UserData.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}
All this is working great. Or it was until I had to change it for a whole new bunch of users. The problem with the new users is that the "userFunctions" variable can get really long, and is way too long to store in a cookie (that is limited in size to something like 4k).
I would change my code to store the "userFunctions" in session, but session is not available to Application_AuthenticateRequest. I could possibly store the data in the application cache (maybe in a key/value pair) but I hesitate to do that as the application cache doesn't seem the 'right' place to put this data.
I probably will end up putting it in the application cache, but before I do I thought I'd ask and see if anybody has a better alternative?
Given that I cannot use Session to store user roles (as I cannot retrieve them before Authorization has taken place), and I didn't want the expense of making a trip to the database on every page request, I ended up storing the roles in the Application Cache:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles;
string cachedRoles = (string)HttpContext.Current.Cache.Get("UserFunctions" + id.Name.ToLower());
if (cachedRoles == null)
{
// Reload UserFunctions and add back in to Cache.
cachedRoles = [...code to get UserFunctions from database...];
HttpContext.Current.Cache.Insert("UserFunctions" + id.Name.ToLower(), cachedRoles, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), System.Web.Caching.CacheItemPriority.NotRemovable, null);
}
roles = cachedRoles.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}
It seems to work ok (though with limited testing so far).
Related
I have a credential method to set user credentials via GenericPrincipal. I am using asp.net MVC
public void SetCredentials(HttpContextBase context, string username, bool createPersistenceCookie)
{
FormsAuthentication.SetAuthCookie(username, createPersistenceCookie);
IIdentity identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity,new []{"standart"});
context.User = principal;
}
I want to check User.IsInRole("standart") in controller action, but it returns false.
context.User.IsInRole("standart") //returns false
I want to use context.User in my application, but it returns always false.
I think you used asp.net membership api before. And now you want to create custom principal in your application.
When you send request to server, server uses a new clean HttpContext. So you lost your old informations. If you want to use old session informations is application, you shuld save your data in server or client side. You can do this two way.
Client cookie
Server session
I recommand you to use client cookies. Because data is being stored to client side, so you save server resources.
public void SetCredentials(HttpContextBase context, string username, bool createPersistenceCookie)
{
var formsAuthenticationTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMilliseconds(FormsAuthentication.Timeout.TotalMilliseconds),
createPersistenceCookie,
roles
);
var encryptedTicket = FormsAuthentication.Encrypt(formsAuthenticationTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.AppendCookie(authCookie);
}
I sended encrypted cookie to client side. And I should check this cookie all incoming request to server application.
And now in Global.asax file:
protected void Application_AuthenticateRequest(object sender, System.EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null) return;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
IIdentity identity = new GenericIdentity(ticket.Name);
IPrincipal principal = new GenericPrincipal(identity, ticket.UserData.Split('|'));
HttpContext.Current.User = principal;
}
I hope solve your issue.
How to implement keep me logged in asp.net using login control and membership in asp.net
By adding a checkbox..
And if it's checked you have to create a cookie with authentication and if it's not checked you have to put it in session
http://msdn.microsoft.com/en-us/library/system.web.sessionstate.httpsessionstate.aspx
Another way is to implement a cookie that's not persistent if it's unchecked like that:
int timeout = rememberMe ? 525600 : 30; // Timeout in minutes, 525600 = 365 days.
var ticket = new FormsAuthenticationTicket(userName, rememberMe, timeout);
string encrypted = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encrypted);
cookie.Expires = System.DateTime.Now.AddMinutes(timeout);
cookie.HttpOnly = true; // cookie not available in javascript.
Response.Cookies.Add(cookie);
First you need to create Cookie on login button click as follows and Store the Login Detail in it
protected void btnLogin_Click(object sender, System.EventArgs e)
{
string username = txtUsername.Text;
string Password = txtPassword.Text;
// Create Cookie and Store the Login Detail in it if check box is checked
if ((CheckBox1.Checked == true)) {
HttpCookie mycookie = new HttpCookie("LoginDetail");
mycookie.Values("Username") = txtUsername.Text.Trim();
mycookie.Values("Password") = txtPassword.Text.Trim();
mycookie.Expires = System.DateTime.Now.AddDays(1);
Response.Cookies.Add(mycookie);
}
Response.Redirect("Default2.aspx");
}
then check if cookie exists (is remember me checked), if yes fill the details as follows-
protected void Page_Load(object sender, System.EventArgs e)
{
//check if cookie exist then login page from
if ((Response.Cookies("LoginDetail") != null)) {
//Username
string uname = Response.Cookies("LoginDetail").Values("Username").ToString();
string pass = Response.Cookies("LoginDetail").Values("Username").ToString();
Response.Redirect("Default2.aspx");
}
}
This is the login function (after I validate user name and password, I load user data into "user" variable and call Login function:
public static void Login(IUser user)
{
HttpResponse Response = HttpContext.Current.Response;
HttpRequest Request = HttpContext.Current.Request;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.UserId.ToString(), DateTime.Now, DateTime.Now.AddHours(12), false,
UserResolver.Serialize(user));
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(ticket));
cookie.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(cookie);
string redirectUrl = user.HomePage;
Response.Redirect(redirectUrl, true);
}
UserResolver is the following class:
public class UserResolver
{
public static IUser Current
{
get
{
IUser user = null;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
user = Desrialize(ticket.UserData);
}
return user;
}
}
public static string Serialize(IUser user)
{
StringBuilder data = new StringBuilder();
StringWriter w = new StringWriter(data);
string type = user.GetType().ToString();
//w.Write(type.Length);
w.WriteLine(user.GetType().ToString());
StringBuilder userData = new StringBuilder();
XmlSerializer serializer = new XmlSerializer(user.GetType());
serializer.Serialize(new StringWriter(userData), user);
w.Write(userData.ToString());
w.Close();
return data.ToString();
}
public static IUser Desrialize(string data)
{
StringReader r = new StringReader(data);
string typeStr = r.ReadLine();
Type type=Type.GetType(typeStr);
string userData = r.ReadToEnd();
XmlSerializer serializer = new XmlSerializer(type);
return (IUser)serializer.Deserialize(new StringReader(userData));
}
}
And the global.asax implements the following:
void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
IPrincipal p = HttpContext.Current.User;
if (p.Identity.IsAuthenticated)
{
IUser user = UserResolver.Current;
Role[] roles = user.GetUserRoles();
HttpContext.Current.User = Thread.CurrentPrincipal =
new GenericPrincipal(p.Identity, Role.ToString(roles));
}
}
First question:
Am I do it right?
Second question - weird thing!
The user variable I pass to Login has 4 members: UserName, Password, Name, Id.
When UserResolver.Current executed, I got the user instance.
I descided to change the user structure - I add an array of Warehouse object.
Since that time, when UserResolver.Current executed (after Login), HttpContext.Current.User.Identity.IsAuthenticated was false and I couldn't get the user data.
When I removed the Warehouse[] from user structure, it starts to be ok again and HttpContext.Current.User.Identity.IsAuthenticated become true after I Login.
What is the reason to this weird behaviour?
First, you don't need to do an HttpContext.Current from Global.asax. Global.asax derives from HttpApplication. So all you need to do is to get the Context property. This might help make that code a little cleaner.
//this is all you need in your global.asax
void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if(Context.User.Identity.IsAuthenticated)
{
var user = UserResolver.Current;
Context.User = Thread.CurrentPrincipal = new UserWrapperPrincipal(user, Context.User.Identity);
}
}
//this helper class separates the complexity
public class UserWrapperPrincipal: IPrincipal, IUser
{
private readonly IUser _user;
private readonly IIdentity _identity;
public UserWrapperPrincipal(IUser user, IIdentity identity)
{
_user = user;
_identity = identity;
}
private IList<string> RoleNames
{
get { return _user.GetUserRoles().Select(role => role.ToString()); }
}
public IIdentity Identity { get { return _identity; } }
public bool IsInRole(string role) { return RoleNames.Contains(role); }
}
Based on your error, it seems like the issue is that either your serializing function or your deserializing function corrupts the data. However, the problem area is probably not those functions. Either there is an issue in serializing the Warehouse object (serializing complex types can sometimes be tricky), or in the serialization of the actual array. Since you are using the default .NET XmlSerializer, There is a good article on customizing and controlling the way different objects are handled available at http://www.diranieh.com/NETSerialization/XMLSerialization.htm .
On another note, are you sure that this is the best way for you to store this data in your application? Storing a user-id and name makes sense. When you start storing serialized arrays of complex objects in your cookie, it might indicate you are not approaching the problem correctly to begin with.
I am guessing that your code is in a log on event somewhere and your building a custom forms auth.
You also need to then build the User object from the cookie on every page request
public class AuthHttpModule : IHttpModule {
public virtual void Init(HttpApplication app) {
app.AuthenticateRequest += new EventHandler(app_AuthenticateRequest);
}
private void app_AuthenticateRequest(object source, EventArgs e) {
HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie == null) {
HttpContext.Current.User = null;
} else {
cookie = HttpContext.Current.Response.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(ticket), new string[0]);
}
bool result = HttpContext.Current.Request.IsAuthenticated;
}
}
EDIT
Try adding this to your global
void Application_AuthenticateRequest(Object sender, EventArgs e)
HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null) {
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(ticket), new string[0]);
}
}
OK, here's my code to create an authentication cookie:
// get user's role
List<UserType> roles = rc.rolesRepository.GetUserRoles(rc.userLoginRepository.GetUserID(userName));
List<string> rolesList = (from r in roles
select r.ToString()).ToList();
string[] rolesArr = rolesList.ToArray();
// create encryption cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddDays(90),
createPersistentCookie,
String.Join(";",rolesArr) //user's roles
);
// add cookie to response stream
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
//FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
And here's my code in Global.asax to set the user roles into the user identity:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = authTicket.UserData.Split(new char[] { ';' });
if (Context.User != null)
{
Context.User = new System.Security.Principal.GenericPrincipal(Context.User.Identity, roles);
}
}
catch
{
return;
}
}
However, if "createPersistentCookie" is TRUE in the top example, no persistent cookie is created. If I uncomment the last line like so:
//System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
then the persistent cookie is created on my hard drive. BUT then in the Global.asax code, the UserData field in "authTicket" is blank, so I can't set up the roles properly!
So I have to use SetAuthCookie to create a persistent cookie, but then for some reason the UserData field disappears from the persistent cookie.
What is the answer to this??
To create a persistent cookie you need to set the Expires property:
if (authTicket.IsPersistent)
{
authCookie.Expires = authTicket.Expiration;
}
When a user logins into my site i create the following authenticate ticket:
// Create the authentication ticket
var authTicket = new FormsAuthenticationTicket(1, // Version
userName, // Username
DateTime.UtcNow, // Creation
DateTime.UtcNow.AddMinutes(10080), // Expiration
createPersistentCookie, // Persistent
user.Role.RoleName + "|~|" + user.UserID + "|~|" + user.TimeZoneID); // Additional data
// Encrypt the ticket
var encTicket = FormsAuthentication.Encrypt(authTicket);
// Store the ticket in a cookie
HttpContext.Current.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket) { Expires = authTicket.Expiration });
Then in my Global.asax.cs file i have the following:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Get the authentication cookie
var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
// If it exists then decrypt and setup the generic principal
if (authCookie != null && !string.IsNullOrEmpty(authCookie.Value))
{
var ticket = FormsAuthentication.Decrypt(authCookie.Value);
var id = new UserIdentity(ticket); // This class simply takes the value from the cookie and then sets the properties on the class for the role, user id and time zone id
var principal = new GenericPrincipal(id, new string[] { id.RoleName });
HttpContext.Current.User = principal;
}
}
protected void Session_Start(object sender, EventArgs e)
{
// If the user has been disabled then log them out
if (Request.IsAuthenticated)
{
var user = _userRepository.Single(u => u.UserName == HttpContext.Current.User.Identity.Name);
if (!user.Enabled)
FormsAuthentication.SignOut();
}
}
So far so good. The problem i have is that if an administrator changes a user's role or time zone then the next time they return to the site their ticket is not updated (if they selected remember me when logging in).
Here's my authentication settings incase it helps:
<authentication mode="Forms">
<forms timeout="10080" slidingExpiration="true" />
</authentication>
<membership userIsOnlineTimeWindow="15" />
I've been reading up on slidingExpiration but as far as i can tell it only increases the expiration time and doesn't renew the contents of the cookie. I'd really appreciate it if someone could help. Thanks
I simply changed my Session_Start to:
// If the user is disabled then log them out else update their ticket
if (Request.IsAuthenticated)
{
var user = _userRepository.Single(u => u.UserName == HttpContext.Current.User.Identity.Name);
if (!user.Enabled)
FormsAuthentication.SignOut();
else
RenewTicket(); // This calls the same code to create the cookie as used when logging in
}
My proposal would be to do another cookie for the remember.
This way session info can be in-memory cookie, while remember me cookie can be set to persist.