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

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();

Related

Can't implement Remember Me functionality to FormsAuthentication

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)

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);

Logging out of ASP.NET MVC application with windows authentication and being able to login again with same user

I am building an application in ASP.NET MVC with windows authentication.
I need a way to logout the logged in user such that a new user can log into the same application without having to close the browser.
For this, I found a neat solution which is as below:
public ActionResult LogOut()
{
HttpCookie cookie = Request.Cookies["TSWA-Last-User"];
if(User.Identity.IsAuthenticated == false || cookie == null || StringComparer.OrdinalIgnoreCase.Equals(User.Identity.Name, cookie.Value))
{
string name = string.Empty;
if(Request.IsAuthenticated)
{
name = User.Identity.Name;
}
cookie = new HttpCookie("TSWA-Last-User", name);
Response.Cookies.Set(cookie);
Response.AppendHeader("Connection", "close");
Response.StatusCode = 0x191;
Response.Clear();
//should probably do a redirect here to the unauthorized/failed login page
//if you know how to do this, please tap it on the comments below
Response.Write("Unauthorized. Reload the page to try again...");
Response.End();
return RedirectToAction("Index");
}
cookie = new HttpCookie("TSWA-Last-User", string.Empty)
{
Expires = DateTime.Now.AddYears(-5)
};
Response.Cookies.Set(cookie);
return RedirectToAction("Index");
}
The problem with this approach however is that the same user cannot login again. It always needs to be a different user to the current one.
I am thinking I should be able to do this this by changing the if clause.
I tried removing the StringComparer.OrdinalIgnoreCase.Equals(User.Identity.Name, cookie.Value) condition as well but it fails to work since cookie value could be not null.
Please, check this post! It worked for me!
https://stackoverflow.com/a/3889441
Just in case, i'm pasting here the code:
#Palantir said:
That's strange... I make one single call to:
FormsAuthentication.SignOut(); and it works...
public ActionResult Logout() {
FormsAuthentication.SignOut();
return Redirect("~/");
}

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.

Is Roles.IsUserInRole behaving as expected in the following simple scenario?

In a custom role provider (inheriting from RoleProvider) in .NET 2.0, the IsUserInRole method has been hard-coded to always return true:
public override bool IsUserInRole(string username, string roleName) { return true; }
In an ASP.NET application configured to use this role provider, the following code returns true (as expected):
Roles.IsUserInRole("any username", "any rolename"); // results in true
However, the following code returns false:
Roles.IsUserInRole("any rolename"); // results in false
Note that User.IsInRole("any rolename") is also returning false.
Is this the expected behavior?
Is it incorrect to assume that the overload that only takes a role name would still be invoking the overridden IsUserInRole?
Update: Note that there doesn't seem to be an override available for the version that takes a single string, which has led to my assumption in #2.
I looked at Roles.IsUserInRole(string rolename) in .net reflector, and it resolves to the following:
public static bool IsUserInRole(string roleName)
{
return IsUserInRole(GetCurrentUserName(), roleName);
}
I would take a look at your current user. Here's why:
private static string GetCurrentUserName()
{
IPrincipal currentUser = GetCurrentUser();
if ((currentUser != null) && (currentUser.Identity != null))
{
return currentUser.Identity.Name;
}
return string.Empty;
}
I would be willing to bet this is returning an empty string because you either don't have a Current User, or its name is an empty string or null.
In the IsUserInRole(string username, string roleName) method, there is the following block of code right near the beginning:
if (username.Length < 1)
{
return false;
}
If your GetCurrentUserName() doesn't return anything meaningful, then it will return false before it calls your overridden method.
Moral to take away from this: Reflector is a great tool :)
Also beware if you have selected cacheRolesInCookie="true" in the RoleManager config. If you have added a new role to the database, it might be looking at the cached version in the cookie.
I had this problem and the solution was to delete the cookie and re-login.
This may help someone - be aware:
If you are using the login control to authenticate - the username entered into the control becomes the HttpContext.Current.User.Identity.Name which is used in the Roles.IsUserInRole(string rolename) and more specifically - the membership's GetUser() method. So if this is the case make sure you override the Authenticate event, validate the user in this method and set the username to a value that your custom membership provider can use.
protected void crtlLoginUserLogin_Authenticate(object sender, AuthenticateEventArgs e)
{
bool blnAuthenticate = false;
string strUserName = crtlLoginUserLogin.UserName;
if (IsValidEmail(strUserName))
{
//if more than one user has email address - must authenticate by username.
MembershipUserCollection users = Membership.FindUsersByEmail(strUserName);
if (users.Count > 1)
{
crtlLoginUserLogin.FailureText = "We are unable to determine which account is registered to that email address. Please enter your Username to login.";
}
else
{
strUserName = Membership.GetUserNameByEmail(strUserName);
blnAuthenticate = Membership.ValidateUser(strUserName, crtlLoginUserLogin.Password);
//setting the userLogin to the correct user name (only on successful authentication)
if (blnAuthenticate)
{
crtlLoginUserLogin.UserName = strUserName;
}
}
}
else
{
blnAuthenticate = Membership.ValidateUser(strUserName, crtlLoginUserLogin.Password);
}
e.Authenticated = blnAuthenticate;
}

Resources