Why my users get logged out when the session gets restarted? - c#

I have this application that uses custom methods to register and loggin users using FormsAuthentication. The server where this is hosted has a policy of restarting the sessions every 15 minutes and when that happens all my users get logged out. The code to loggin a user is:
var user = this.accountRepo.GetUser(id);
// Create the forms authentication cookie
var cookieValue = user.name;
HttpCookie cookie = FormsAuthentication.GetAuthCookie(cookieValue, true);
// Dercrypt the cookie
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
// Create a new ticket with the desired data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket
(
ticket.Version,
ticket.Name,
ticket.IssueDate,
DateTime.Now.AddYears(1),
true,
user.Authentication
);
// Update the cookies value
cookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Set(cookie);
accountRepo.Login(user);
With the Forms cookie created and with my Authentication data, which is basically the users hashed password, I then use the following logic to display the Login button or the username:
#{
var accountRepo = new AccountRepository();
var user = accountRepo.GetCurrentUser();
}
#if(user != null && user.LoggedIn) {
<div>#Html.ActionLink(Context.User.Identity.Name + " - Logout", "LogOff", "Account", null, new { #class = "logout_link" })</div>
}
else
{
#Html.ActionLink("Login", "Login", "Account", new { returnUrl = Request.Url.AbsoluteUri }, new { #class = "login_link" })
}
And that "GetCurrentUser()" method is:
var cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (cookie != null)
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
return db.Users.SingleOrDefault(u => u.Authentications.Equals(ticket.UserData, StringComparison.CurrentCultureIgnoreCase));
}
return null;
Am I missing something here? I believe that with this code It should matter if the session restarts, my users should stay logged in.
Thanks in advance.

It's just as Mystere Man said. The cookie name was getting re-generated every time the session rebooted, so the app was looking for the cookie with a different name than what it had before.
For the peace of mind of all of you that helped me, and for the developer that will support this app in the future, I refactored it so its not that "evil" anymore :P

Related

How do I reference the HttpContext.Current.User after the Application_PostAuthenticateRequest in MVC?

So here's a break down of my issue in steps:
The user logs in with Google.
On the login callback, information about the user is gathered
Roles are assigned based on the user
A FormsAuthenticationTicket is created which passes the user/roles to the Application_PostAuthenticateRequest in Global.asax
In that request a GenericPrinciple is created from the authentication ticket and roles
HttpContext.Current.User is set to the above variable
Now my question is, now that I set who the current user is that is using the website, how can I reference them? After the post authenticate complete, I check the current user and it's null. Should I be setting the principle to a different variable other than HttpContext.Current.User?
Callback
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
...
// Get roles for current user
string roles = "bob,adminbob,cthulu";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
loginInfo.Email,
DateTime.Now,
DateTime.Now.AddMinutes(30), // value of time out property
false,
roles,
FormsAuthentication.FormsCookiePath);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
Response.Cookies.Add(authCookie);
...
}
Post Authenticate in Global.asax
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) {
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null) {
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] userRoles = authTicket.UserData.Split(new Char[] { ',' });
GenericPrincipal userPrincipal = new GenericPrincipal(new GenericIdentity(authTicket.Name), userRoles);
HttpContext.Current.User = userPrincipal; //How do I reference this in the program?
}
}
Nevermind I figured out why it was returning blank. When I was calling the method to check the Name, the Identity wasn't authenticated yet. If you try to get the name without it being authenticated, it returns blank.
So I just simply checked with:
HttpContext.Current.User.Identity.IsAuthenticated

MVC 5 Global User Account Object

I have a application with the following Layout. In the Shared Views Folder I have, _Layout.cshtml, _SideNav.cshtml and _CurrentUser.cshtml.
In the _Layout.cshtml I have:
#{ Html.RenderPartialIf("_SideNav", Request.IsAuthenticated); }
In the _SideNav.cshtml I have:
#{ Html.RenderPartial("_CurrentUser"); }
In the _CurrentUser.cshtml I have:
<div class="login-info">
<span>
<a href="javascript:void(0);" id="show-shortcut" data-action="toggleShortcut">
<img src="~/content/img/avatars/sunny.png" alt="me" class="online" />
<span>#User.Identity.Name</span>
<i class="fa fa-angle-down"></i>
</a>
</span>
</div>
We use FormsAuthentication to authenticate a user. We are not using the standard Identity authentication which ships with ASP.Net MVC 5 because we are using a LDAP Server.
FormsAuthentication.SetAuthCookie(username, isPersistent);
.....
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(username, "Forms"), roles);
We use the username in the cookie so that we can easily get information from the LDAP server.
Problem: #User.Identity.Name returns that username. But I need to display the full name of the user. I have access to the full name when we authenticate. but not sure how to use it.
How can I pass the FullName value from the AccountController to the _CurrentUser.cshtml partial view? Kind of like a Global Container like #User.Identity with more attributes that can be set.
You can use something like this
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
viewModel.Email,
YOUR_ISSUE_DATE,
YOUR_EXPIRE_DATE,
viewModel.RememberMe,
JsonConvert.SerializeObject(user),
FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie authcookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
Response.Cookies.Add(authcookie);
You can try something similar to-->
public static MyAuthenticationTicket GetIMyUserTicket()
{
//Get custom user data object from forms auth cookie
MyAuthenticationTicket result= null;
if (HttpContext.Current.User == null)
return null;
if (!HttpContext.Current.Request.IsAuthenticated)
return null;
FormsIdentity ident = (FormsIdentity)HttpContext.Current.User.Identity;
if (ident == null)
return null;
FormsAuthenticationTicket ticket = ident.Ticket;
if (ticket == null)
return null;
if (!FormsAuthentication.CookiesSupported)
{
//If cookie is not supported for forms authentication, then the
//authentication ticket is stored in the Url, which is encrypted.
//So, decrypt it
ticket = FormsAuthentication.Decrypt(ident.Ticket.Name);
}
string userDataString = ticket.UserData;
if(!String.IsNullOrEmpty(userDataString))
result= new MyAuthenticationTicket(userDataString);
return result;
}
Use ClaimsIdentity.
When the user login to your app add his name to your identity.
ClaimsIdentity claims = new ClaimsIdentity();
claims.AddClaim(new Claim("name", "value"));
Then create a extension method to get your name
public static string GetName(this IPrincipal principal)
{
return ((ClaimsIdentity)principal.Identity).Claims.Where(x => x.Type == "name").FirstOrDefault().Value;
}
Now use it as #User.GetName()
Update:
Or in you login controller use UserManager to get the user identity.
Like this:
ClaimsIdentity claims = UserManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
claims.Add(new Claim("name" , "value"));
AuthenticationManager.SignIn(claims);

Custom MVC Authentication without SetAuthCookie()

Because of the requirements of my project, I'm wanting to provide custom authenticate for my MVC controller actions. Therefore, I will not be using SetAuthCookie().
Intially I set a cookie as follows;
string userData = EncDec.MakeString(user.Email + "|" + user.UserId);
//the Cookie and FormsAuthenticationTicket expiration date/time is the same
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
user.UserName, // authenticated username
DateTime.Now, // issueDate
cookieExpiry, // expiryDate
false, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
//create the cookie
HttpCookie cookie = new HttpCookie("ADV_" + Extensions.ControllerExtensionMethods.GetGuid(this), encryptedTicket);
cookie.Secure = true;
cookie.HttpOnly = true;
cookie.Expires = cookieExpiry;
Response.Cookies.Add(cookie);
The HttpCookie is being saved in the client browser with a encrypted FormsAuthenticationTicket.
Then within my controller actions, whenever I need to check and verify that the user is authenticated I call this method;
public static FormsAuthenticationTicket IsAuthenticated(string guid)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["ADV_" + guid];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
if (!ticket.Expired)
{
//if the user is authenticated and the cookie hasn't expired we increase the expiry of the cookie - keep alive
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
//create a new ticket based on the existing one
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
ticket.Version, // ticket version
ticket.Name, // authenticated username
ticket.IssueDate, // issueDate
cookieExpiry, // expiryDate, changed to keep alive if user is navigating around site
false, // true to persist across browser sessions
ticket.UserData, // can be used to store additional user data
ticket.CookiePath); // the path for the cookie
string newEncryptedTicket = FormsAuthentication.Encrypt(newTicket);
//keep alive
HttpCookie newCookie = new HttpCookie("ADV_" + guid, newEncryptedTicket);
newCookie.Secure = true;
newCookie.HttpOnly = true;
newCookie.Expires = cookieExpiry;
HttpContext.Current.Response.Cookies.Set(newCookie);
return newTicket;
}
}
return null;
}
Every time the user is re-authenticated, I am increasing the time out of when the cookie will expire, so that the login is keep alive.
Everything seems to work fine, and the users are correctly authenticated, and if they aren't authenticated I redirect them to a login page, and they can't access methods if they aren't authenticated either.
My questions are:
Is this way of dealing with the authentication secure.
Is there anything I should be aware of, in terms of a security risk.
Thanks.
You basically need to look at creating a Custom Authentication Attribute.
I'm not going to provide the actual implementation here, but this will put you on the right path.
Here's the basic representation :-
public class GoogleAuthAttribute : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
IIdentity ident = filterContext.Principal.Identity;
if (!ident.IsAuthenticated || !ident.Name.EndsWith("#google.com"))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "GoogleAccount"},
{"action", "Login"},
{"returnUrl", filterContext.HttpContext.Request.RawUrl}
});
}
}
}
I've took this from the Apress Book i'm currently reading on MVC5. If OnAuthentification fails, you set the Result property of the AuthenticationContext, this is then passed to the AuthenticationChallengeContext, where you can add your challenge code.
In my example, the user is redirected to the login page.
All you need t do, is place this AuthentificationAttribute on the Action Methods you require.
You should be able to build in or work your custome security code in to this.
You should really ask yourself if its a good idea to be adding custom security measures, as it can lead to more problems that you want.

ASP.MVC Remember me cookie not working after session time out

On a login form I have an option to allow the user to click a remember me checkbox which creates a new FormsAuthenticationTicket which then gets added to a cookie.
if (_model.RememberMe)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
_model.Username,
DateTime.Now,
DateTime.Now.AddDays(30),
true,
_model.Username,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Which should hopefully be in the clients browser for 30 days as stated above.
Testing this, I've purposely left the current session timeout for only a minute
<sessionState timeout="1"></sessionState>
So after a minute, if the user has said "remember me" I expect the website should not be redirected back to the login page. However it does. This is the code that does it.
// [".ASPXAUTH"] is the cookie name that is created by the FormsAuthenticationTicket`
if (User.Identity.Name == "" && Request.Cookies[".ASPXAUTH"] == null)
{
return RedirectToAction("LogOut", "Login");
}
// the current session hasn't timed out or the remember me cookie is enabled
FormsIdentity id = (FormsIdentity)User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
But the cookie is NULL.
I am expecting it's a misunderstanding on my behalf so if anyone can give me a hand. I would be very grateful.
Thanks
What you are looking for is
string mySessionCookie = System.Web.HttpContext.Current.Request.Headers["Cookie"];
if (mySessionCookie.IndexOf(".ASPXAUTH", StringComparison.Ordinal) >= 0) {
// do something
}
EDIT
How about this, I haven't tested it but I remember doing something like this before
HttpCookie cookie = (HttpCookie)(Request.Cookies[FormsAuthentication.FormsCookieName]);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);

FormsAuthentication won't save UserData in default MVC4 Web Application template

I've been fighting with this for a while. In VS 2012 I created a new MVC4 application using the "Internet Application" project template (for simplicity, I'm also seeing the problem in my regular app using an ExtendedMembershipProvider).
On Login I want to put some UserData in the Forms Authentication cookie, so I use the following code:
public ActionResult Login(LoginModel model, string returnUrl)
{
Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(model.UserName, true);
string userData = "This is some test data.";
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newAuthTicket = new FormsAuthenticationTicket(authTicket.Version, authTicket.Name, authTicket.IssueDate, authTicket.Expiration, authTicket.IsPersistent, userData);
string newAuthTicketEncrypted = FormsAuthentication.Encrypt(newAuthTicket);
authCookie.Value = newAuthTicketEncrypted;
Request.Cookies.Set(authCookie);
// Response.Write("Encrypted cookie value: " + authCookie.Value); // value here differs than what browser sees
// Response.Write("UserData: " + FormsAuthentication.Decrypt(authCookie.Value).UserData + "<br/>"); // userdata is present here.
// return, shortened for brevity
}
}
Pretty basic. However it is not present in the cookie when I decrypt it. The problem seems to be that something is creating a new forms authentication cookie somewhere else down in the pipeline. I can prove this by printing out the value of the encrypted cookie, and comparing it to the value that appears in my browser after the login request. They are different! Something is recreating the cookie and encrypting it, without UserData present. The name value is present in the cookie - any idea where or what would be doing this? Did MS break UserData in forms authentication with the new WebMatrix methods?
You're setting the cookie on the request you need to set the cookie on the Response object.

Categories