Assigning User.Identity.Name to variable keeps being null - asp.net

I'm using asp.net identity in my asp.net core mvc core 5 project.
I have a controller, where I need to save the name and email of the currently logged in user in two variables.
So, I now have this controller action:
public string GetUser(string name)
{
string u = User.Identity.Name;
if (u != null)
{
return u;
}
}
Right now, I'm simply trying to save the name of the current user in a variable.
I start the program in debug mode, while I am logged in.
I have a breakpoint exactly on the line string u = User.Identity.Name;.
If I hover hover User.Identity.Name then I can see that the expression evaluates to the name of the currently logged in user.
But when I then step over to the next line, I can see that u evaluates to null.
Thus the if statement evaluates to false and is not executed.
If I write the action like this instead:
public string GetUser(string name)
{
if (User.Identity.Name != null)
{
return User.Identity.Name;
}
return "what";
}
Then everything works as it should.
Why is this? why can I not assign the currently logged in user to any variable?

Related

Log all other browsers out on login

I have a project running MVC4 and using Simple Membership for authentication. I only want to allow a user to login on one browser. To make this transparent to the user, I need a way to have any other authenticated browser log out whenever a user logs in. This means, if two users are trying to use the same login, they would just continuously kick each other off making that very unproductive.
Right now, I have it set up to only allow a user to login once but if that user were to close the browser and move to another computer, they would be locked out for 30 minutes I can see this creating a number of unnecessary support calls.
I would assume I need to track some sort of identifier in a database and check to make sure it matches with each request otherwise they are logged out. Maybe, adding some sort of cookie.
If anyone has an elegant solution to this, I would appreciate it!
This is what I am currently using to lock users into only one login:
Login:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
string sKey = model.UserName;
string sUser = Convert.ToString(System.Web.HttpContext.Current.Cache[sKey]);
if (sUser == null || sUser == String.Empty)
{
TimeSpan SessTimeOut = new TimeSpan(0, 0, System.Web.HttpContext.Current.Session.Timeout, 0, 0);
System.Web.HttpContext.Current.Cache.Insert(sKey, sKey, null, DateTime.MaxValue, SessTimeOut, System.Web.Caching.CacheItemPriority.NotRemovable, null);
Session["user"] = model.UserName;
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
else
{
ModelState.AddModelError("", "You are already logged in.");
}
return View(model);
}
Global.asax
protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
if (HttpContext.Current.Session != null)
{
if (Session["user"] != (null)) // e.g. this is after an initial logon
{
string sKey = (string)Session["user"];
// replace the last hit with current time
// Accessing the Cache Item extends the Sliding Expiration automatically
string sUser = (string)HttpContext.Current.Cache[sKey];
}
}
}
Logout:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
UserProfile user = db.UserProfiles.SingleOrDefault(s => s.UserName == User.Identity.Name);
string sKey = user.UserName;
System.Web.HttpContext.Current.Cache.Remove(sKey);
WebSecurity.Logout();
return RedirectToAction("Start", "Home");
}
I had used the term session and have removed it. I'm not trying to delete the user's session but make their authorization invalid using web security.
There's nothing built-in for this. You'd have to develop some methodology on your own. You'd basically need two pieces:
Some way of tracking a logged in user across requests. This could be as simple as a table with a username column which you could use to determine if that particular username has been logged in. You'd need to keep this in sync with your logins/logouts of course, and you would also need to store the session id for the user. You'll need that for the next piece:
Some mechanism of removing the session from whatever store it exists in. This would be easiest if you're using SQL sessions, as you could simply delete the row from the table session table with the matching id. There's no way to do this directly with ASP.NET, so you'd have to directly query the database, used a stored procedure, etc.
So, the general idea would be that when a user logs in, you record their username and session id in a table or some other persisted store. When someone attempts to log in, you'd check this store for the username that is being attempted, and if it exists, go delete the session that corresponds to this. The next time the user with that session tries to access a page, their session cookie will no longer match a valid session and they'll be treated as if they've been logged out.

umbraco membership createuser & select profile data

I have created a class to handle membership user creation with custom fields.
I have done it based on this solutions:
How to assign Profile values?
Using ASP .NET Membership and Profile with MVC, how can I create a user and set it to HttpContext.Current.User?
namespace CCL
{
public static MemberProfile CurrentUser
{
get
{
if (Membership.GetUser() != null)
return ProfileBase.Create(Membership.GetUser().UserName) as MemberProfile;
else
return null;
}
}
}
And now I'm trying to use create the user and get the profile data:
if (Membership.GetUserNameByEmail(email) == null)
{
MembershipUser member = Membership.CreateUser(username, password, email);
Roles.AddUserToRole(username, "WebsiteUsers");
CCL.MemberProfile currentProfile = CCL.MemberProfile.CurrentUser;
bool exists = currentProfile != null;
Response.Write(exists.ToString());
}
but currentProfile is returning null.
So I'm unable to assign values from the form to my member custom properties which are handled by the properties set in the class :(
I don't get how I can make it working :(
Does anyone have some thoughts? Thanks
Suggestion 1:
Make sure that ProfileBase.Create returns something that can be cast to a "MemberProfile", otherwise if it can't then casting it will just return NULL.
Suggestion 2:
Make sure the context you are running in has a logged in user, so your call to Membership.GetUser() can find the current user object.
Other thoughts:
The ProfileBase.Create method assumes that the username you pass in is an authenticated user, I'm not sure on it's behavior when the user isn't authenticated..maybe it returns NULL?

About Response.Redirect, FormsAuthentication and MVC

I want to understand the difference in the behavior of a program when we call FormsAuthentication.RedirectFromLoginPage vs. when we call Response.Redirect(FormsAuthentication.GetRedirectUrl()) and manually redirect.
Please see the comments below.
I have a LoginController/Index (two actions, one for HttpGet and one for HttpPost). The View of this controller represents the application's login page.
I also have a home page or landing page, i.e. the page that the user must be taken to after a successful login. This is represented in my application by the HomeController's Index action and the ~Views/Home/Index.cshtml view.
I have presented three scenarios. I understand scenario 1 and I expect it to work the way it does, but I noted a difference in scenarios 2 and 3.
Scenario 1
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control won't reach here
// as expected and the user will be taken to the view that is served
// by the HomeController::Index() action. If it is by convention, it will
// be the ~Views/Home/Index.cshtml view. This is fine.
return View();
}
}
}
Scenario 2
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
Response.Redirect(FormsAuthentication.GetRedirectUrl(loginViewModel.UserName,
loginViewModel.RememberMe));
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. And as expected, it renders the same View, i.e. the View
// associated with the controller we are in, which is ~Views/Login/Index,
// which represents the login page. This is wrong. I shouldn't redirect here.
// I understand this. My question here is two fold:
// 1) I am simply trying to understand the difference in behaviors of the three
// scenarios described in this question.
// 2) Given this, the right way would be to not use Response.Redirect here but instead
// use RedirectToAction. However, if I wanted to use Response.Redirect, what should
// I do?
return View();
}
}
}
Scenario 3
namespace Controllers
{
[AllowAnonymous]
public class LoginController : Controller
{
[HttpPost]
public ActionResult Index(Login loginViewModel)
{
if (ModelState.IsValid)
{
var user = ValidateUser(loginViewModel);
if (user != null)
{
// Other stuff: set cookies, session state, etc.
FormsAuthentication.RedirectFromLoginPage(loginViewModel.UserName,
loginViewModel.RememberMe);
}
else
{
ModelState.AddModelError("", "Invalid password. Please try again.");
}
}
// If the user was a valid user, the flow-of-control still reaches here
// as expected. However, magically, somehow, even though the statement below
// suggests that the user must be taken to the View of the same controller and
// action that we are currently in, i.e. the View of the LoginController::Index()
// action, i.e. the ~Views/Login/Index.cshtml, it magically takes me to the
// ~Views/Home/Index.cshtml instead, which is what is specified as the LoginPage
// attribute of the <authentication>/<forms> element in the web.config.
// I want to know how this happens.
return View();
}
}
}
Update
I am at my wit's end now. Now, even Scenario 1 that uses RedirectToAction is calling the Index() action on the LoginController class.
The actual difference is that FormsAuthentication.RedirectFromLoginPage() sets cookies and then makes redirects but FormsAuthentication.GetRedirectUrl() only returns redirect url.
The funny thing is that implementation of FormsAuthentication.GetRedirectUrl() is like this:
public static String GetRedirectUrl(String userName, bool createPersistentCookie)
{
if (userName == null)
return null;
return GetReturnUrl(true);
}
So actually userName and createPersistentCookie parameters are completely ignored. You must call FormsAuthentication.SetAuthCookie( userName, true/false ) manually before calling GetRedirectUrl.
Agree with Vasily.
RedirectFromLoginPage issues an authentication ticket and places it in the default cookie using the SetAuthCookie method.
You can read something about this behavior here.
If you want to have a better control over the cookie creation you should (encryption, expiration, extending the principal) you should create the cookie yourself.
I explained the whole process here and here.

Update Database Column after X Number of Failed Login Attempts

I have a forms login in my web site using ASP.NET MVC with C#. All of the user profiles are stored in a Customer table that has the following columns:
ID
First_Name
username
password
ActiveWebLog
with ActiveWebLog = 0 meaning the user has not yet activated their account, and ActiveWebLog = 1 meaning the user can login.
This is my Login action in my Controller :
public ActionResult LogOnCustomer()
{
return View();
}
[HttpPost]
public ActionResult LogOnCustomer(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateCustomer(model.UserName, model.Password))
{
this.AuthCustomer = MembershipService.AuthCustomer;
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("RedirectPage", "Account");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
What I want is to set ActiveWebLog = -2 when the user inputs an incorrect username or password 5 times.
Does anyone know how to do that?
Add a LoginAttemptCount column to your table.
Check user credentials against the database.
If no user name, fail. It may be advisable to not tell the user that the username does not exist, because that supports grinding attempts to locate user names.
If the user name matches but the LoginAttemptCount => 5 (or any number), fail.
If the user name matches but the password is bad, increment LoginAttemptCount and fail.
If the user name and password are good (hopefully you are using a hashed password) and LoginAttemptCount < 5, reset LoginAttemptCount to zero.
If you use this methodology, you technically don't need to modify the ActiveWebLog column when the user exceeds the bad password limit (but you certainly could).
I would put the business rules for this inside a separate class library. Your controller will probably have the basic logic to invoke this class library and modify your view based on the resulting status codes.
Session["tries"] = 0;
try
{
User.Login();
}
catch (Exception)
{
Session["tries"] = Session["tries"] + 1;
}
if (Session["tries"] > 4)
{
ActiveWebLog = -2;
}

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