Can't implement Remember Me functionality to FormsAuthentication - asp.net

I'm using FormsAuthentication for logging. What I want is when user checks "Remember Me" option, make cookie never expire. My problem is whenever I try to change cookie expiration time either Request.IsAuthenticated becomes false withResponse.Cookies[FormsAuthentication.FormsCookieName].Value equals null or Request.IsAuthenticated returns always true. My codes are below.
[HttpPost]
[AllowAnonymous]
public ActionResult Login(PortalUserModel model)
{
if (ModelState.IsValid)
{
uzm_portaluser user = _defDal.GetPortalUser(model.Username, model.Password);
if (user != null)
{
FormsAuthentication.SetAuthCookie(user.Id.ToString(), true);
if (model.isRemember)
{
//Tried to increase expire time here using various ways but none of them worked
}
System.Web.HttpContext.Current.Session["user"] = user;
return RedirectToAction(defaultAction, defaultController);
}
else
{
ViewBag.Error = "Wrong username or password";
return RedirectToAction("Login", "Home");
}
}
else
{
ViewBag.Error = "Wrong username or password";
return RedirectToAction("Login", "Home");
}
}
I tried these but as I said none of them worked.
https://stackoverflow.com/a/4851344
(This one makes Request.IsAuthenticatedtrue but Response.Cookies[FormsAuthentication.FormsCookieName] is empty. Most of the solutions are like this)
https://stackoverflow.com/a/10773495/2564339
(This one did not work at all)

Related

ASP.NET display ModelState.AddModelError

I have this method here, which is a login method and if the username and password is incorrect a ModelError gets added.
[HttpPost]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect");
}
}
return RedirectToAction("Index", "Home");
}
My question is how would I display the ModelError to my View Index.cshtml?
... how would I display the ModelError to my View Index.cshtml?
Showing Error message
I initially made the assumption that you wanted to redirect to the home page (HomeController action Index) based on your call to return RedirectToAction("Index", "Home"); at the bottom of the Login action. Now I am thinking maybe this is a mistake in your flow and you are actually trying to show the error message to the user without redirecting and that you only want to redirect if everything succeeds. IF this is the case then just read this part and skip the rest about how to persist model state across RedirectToAction calls. All you need to do is call View instead of RedirectToAction if there is a failure.
[HttpPost]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(ReturnUrl) && ReturnUrl.Length > 1 && ReturnUrl.StartsWith("/")
&& !ReturnUrl.StartsWith("//") && !ReturnUrl.StartsWith("/\\"))
{
return Redirect(ReturnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect");
}
}
return View(model); // goes back to the login view with the existing model and validation error
// return RedirectToAction("Index", "Home");
}
Login.cshtml include the following somewhere
#Html.ValidationSummary()
Working with RedirectToAction
The reason it is not working is the ViewData, which includes the validation messages, is lost when you execute the RedirectToAction. There are a couple of options for a solution.
Keep either persist the ViewData across the RedirectToAction and then restore it.
Only persist the ModelState and then merge it with the ModelState once the new action specified in the RedirectToAction has executed.
Persist ViewData
In most scenarios this works just fine but could cause problems if your redirected to action (in this case Index on the HomeController) has its own ViewData that it depends on.
LoginController.cs
// simplified code to just show the relevant parts to reproduce the problem/solution
[HttpPost]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
// ... some other code
ModelState.AddModelError("", "The user name or password provided is incorrect");
// ... some other code
if (!ModelState.IsValid)
TempData["ViewData"] = ViewData;
return RedirectToAction("Index", "Home");
}
HomeController.cs
public ActionResult Index()
{
if (TempData["ViewData"] != null)
{
// restore the ViewData
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
return View();
}
Home\Index.cshtml
#Html.ValidationSummary()
Merge the ModelState
This is would be my recommended approach because you define how you want this to happen once on a custom ActionFilter attribute and then apply where you want it to occur. You could also put this code directly into your controller but that would violate the DRY principle as soon as you need to do this on multiple controllers.
The approach here is to write the model state to the TempData if the TempData does not already contain a "ModelState" key. If there is already a key present that means that the current request has just written to it and we can read from it and merge that with our existing model state. This will prevent the code from unintentionally overwriting the ViewState or the ModelState as the ModelState is now merged. I can only see this going wrong if there are multiple RedirectToActions that all choose to write to the ModelState but I do not think this is a likely scenario.
ModelStateMergeFilterAttribute.cs
public class ModelStateMergeFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// write to the temp data if there is modelstate BUT there is no tempdata key
// this will allow it to be merged later on redirect
if (filterContext.Controller.TempData["ModelState"] == null && filterContext.Controller.ViewData.ModelState != null)
{
filterContext.Controller.TempData["ModelState"] = filterContext.Controller.ViewData.ModelState;
}
// if there is tempdata (from the previous action) AND its not the same instance as the current model state THEN merge it with the current model
else if (filterContext.Controller.TempData["ModelState"] != null && !filterContext.Controller.ViewData.ModelState.Equals(filterContext.Controller.TempData["ModelState"]))
{
filterContext.Controller.ViewData.ModelState.Merge((ModelStateDictionary)filterContext.Controller.TempData["ModelState"]);
}
base.OnActionExecuted(filterContext);
}
}
LoginController.cs
// simplified the code to just show the relevant parts
[HttpPost]
[ModelStateMergeFilter]
public ActionResult Login(LoginClass model, string ReturnUrl)
{
ModelState.AddModelError("", "The user name or password provided is incorrect");
return RedirectToAction("Index", "Home");
}
HomeController.cs
[ModelStateMergeFilter]
public ActionResult Index()
{
return View();
}
Home\Index.cshtml
#Html.ValidationSummary()
References
Here are some references that also detail some of these approaches. I also relied on some of the input from these previous answers for my answer above.
How do I maintain ModelState errors when using RedirectToAction?
How can I maintain ModelState with RedirectToAction?
If your are using MVC, you can use Validation Summary.
#Html.ValidationSummary();
https://msdn.microsoft.com/en-CA/library/dd5c6s6h%28v=vs.71%29.aspx
Also,pass your model back to your view:
return RedirectToAction("Index", "Home", model);

Strange behavior of Membership.GetUser() in ASP.NET MVC

I have this code:
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
//I added this just to check if it returns true.
bool check = Membership.ValidateUser(model.UserName, model.Password);
if (Membership.ValidateUser(model.UserName, model.Password))
{
//trying to get get the name here.
string name = Membership.GetUser().UserName;
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
string[] roles = Roles.GetRolesForUser(User.Identity.Name);
switch (roles[0])
{
case "Employee":
return RedirectToAction("Index", "Employee");
case "HR_Team":
return Redirect("");
case "Team_Lead":
return Redirect("");
case "Management":
return Redirect("");
}
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
The code was working absolutely fine but I don't know why it stopped responding properly. Now I'm not able to get the user name as well as it's role (that's probably because it's not getting the username I guess). I've no idea at all how it happened. Somebody please help getting rid of this problem. I appreciate any help. Thanks a lot in advance.
Make sure you have specified the username when getting the user because you have not yet set the forms authentication cookie (only after you redirect you will be able to use this overload that doesn't take any argument).
Also what's the point of calling Membership.GetUser().UserName; when you already have the username in model.UserName?
Same thing stands for the roles:
string[] roles = Roles.GetRolesForUser(model.UserName);
Don't attempt to use User.Identity.Name in your LogOn method because you don't have an authenticated user yet.
And if you wanted to get the user use:
var user = Membership.GetUser(model.UserName);
instead of:
var user = Membership.GetUser();

MVC 3 ReturnUrl redirection not working

I have a probably very silly question but I'll has it anyway.
Here is the code in my controller for logging in
[HttpPost]
public ActionResult Index(LogonModel model, string ReturnUrl)
{
ReturnUrl = Request.QueryString["ReturnUrl"];
if (ModelState.IsValid)
{
if (UserRepository.validLogin(model.Username, model.Password))
{
UserLogRepository.createLogEntry("Log On", " has logged on to the Staff Portal.", "Entry/Exit");
if (ReturnUrl.Length > 1)
{
return Redirect(Request.QueryString["ReturnUrl"]);
}
else
{
return RedirectToAction("Dashboard", "Home");
}
}
else
{
ModelState.AddModelError("", Session["Error"].ToString());
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
As you can see i'm just checking if the returnurl has a length for testing purposes before I lock it down more. My issue is I get an "Object reference not set to an instance of an object." pointing to this line "if (ReturnUrl.Length > 1)"
Now the URL I have when a user has timed out from the site is this:
http://localhost/Dispatch2012/Staff/Home?ReturnUrl=Dispatch2012%2FStaff%2FCredential
As you can see, this is the standard redirect created by MVC 3 and i've tried to read the ReturnUrl as a standard query string but every time it says that object doesn't exist. What am I missing?
The way your controller is set up is strange to me, but let's dive into it:
[HttpPost]
public ActionResult Index(LogonModel model, string returnUrl) //changed
{
ReturnUrl = returnUrl; //changed
if (ModelState.IsValid)
{
if (UserRepository.validLogin(model.Username, model.Password))
{
UserLogRepository.createLogEntry("Log On", string.Format("{0} has logged on to the Staff Portal.", model.Username, "Entry/Exit"); //changed
if (ReturnUrl.Length > 1) //this should use IsLocalUrl
{
return Redirect(Request.QueryString["ReturnUrl"]);
}
else
{
return RedirectToAction("Dashboard", "Home");
}
}
else
{
ModelState.AddModelError("", Session["Error"].ToString());
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
A few things:
Your returnUrl needs to be checked to make sure it's a Local URL. There are a number of ways of doing this, and since you're using ASP.NET MVC 3, it's built in.
Why are you pulling the ReturnUrl out of the querystring when (if you've set up your view correctly), it's already passed in?
Do you have the following in your view?
<%= Html.Hidden("returnUrl", Url.Encode(Url.Action("ActionToRedirectTo", "ControllerName", new { id = Model.Whatever}))) %>
If so, when it posts, it will automatically get sent to the Index Action as the returnUrl parameter.
I'm betting since it's not working, you aren't actually sending the ReturnUrl back correctly, check what I said about the view. Oh, and make sure you're URL encoding the ReturnUrl.
Also, since it's a HttpPost, the querystring wouldn't have the return Url in it.

Redirecting to mobile page in Asp.Net MVC 3

I'm trying to use a different login page for an Asp.Net MVC application that is modified to fit mobile devices, primarily iPhone/Android. All I basically need is to modify the login view, because the actual content is in a particular part of the application, I'm not trying to make a mobile version of the entire site.
So I tried to follow this: http://www.asp.net/learn/whitepapers/add-mobile-pages-to-your-aspnet-web-forms-mvc-application
But I don't know the authentication well enough to know exactly how to do the logon action methods for the mobile version. I feel like I'm probably missing a specific mobile post action,and I don't understand what to do with the url passed in the redirect. Here's what I've got so far:
public ActionResult LogOn()
{
string returnUrl = Request.QueryString["ReturnUrl"];
if ((returnUrl != null) && returnUrl.StartsWith("/Mobile/",
StringComparison.OrdinalIgnoreCase))
{
return RedirectToAction("LogOnMobile", "Account",
new { ReturnUrl = returnUrl });
}
return View();
}
public ActionResult LogOnMobile(string returnurl)
{
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
And this doesn't work. I get to the mobile login page (or actually so far I've just tried out that the action method works by commenting out the if clause), but when I try to login I just get to the same page again, but strangely without the fields...
What do I need to do to get this to work?
Do you keep the account controller in the Mobile Area? Better practice will be using the mobile Area to keep all your mobile site stuff and redirect the authorization to the AccountController.
public ActionResult Login()
{
string returnUrl = Request.QueryString["ReturnUrl"];
if ((returnUrl != null) && returnUrl.StartsWith("/Mobile/", StringComparison.OrdinalIgnoreCase))
return RedirectToAction("Login", "Account", new { Area = "Mobile", ReturnUrl = returnUrl });
return ContextDependentView();
}
I don't see anywhere you are actually checking to see if it is a mobile device to re-direct:
if (Request.Browser.IsMobileDevice){ }

ASP.Net MVC Cookies not persisting

Essentially I'm trying to set a cookie after a user logs in to persist their username for the next time they log in. Here's my code to set the cookie. When I look at the site cookies in Firefox as soon as the cookie is set, it shows the sessionID cookie, but not the one I just set. When I check the headers in Fiddler, I don't see it setting the cookie, only my sessionID cookie.
HttpCookie hc = new HttpCookie("username", model.UserName);
hc.Expires = DateTime.Now.AddYears(1);
System.Web.HttpContext.Current.Request.Cookies.Add(hc);
Here is where I check to see if the cookie exists.
if (System.Web.HttpContext.Current.Request.Cookies["username"] != null)
Here's the full context of the methods in question
public ActionResult LogOn()
{
if (System.Web.HttpContext.Current.Request.Cookies["username"] != null)
return View(new LogOnModel { UserName = System.Web.HttpContext.Current.Request.Cookies["username"].Value });
else
return View();
}
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
HttpCookie hc = new HttpCookie("username", model.UserName);
hc.Expires = DateTime.Now.AddYears(1);
System.Web.HttpContext.Current.Request.Cookies.Add(hc);
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return View(model);
}
Add to response.cookies not request.cookies

Resources