MVC 4 SimpleMembership - Why WebSecurity.CurrentUserId -1 after login - asp.net-mvc

I am trying to set a cookie upon login and having issues with getting the current user id after login. In the below, intUserId is -1 and WebSecurity.IsAuthenticated is false. Is this not the correct place to put this code? After this, it redirects to the home page...so not sure why this is not the correct place.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
//TODO: set current company and facility based on those this user has access to
int intUserId = WebSecurity.GetUserId(User.Identity.Name);
int intUserId2 = WebSecurity.GetUserId(model.UserName);
UserSessionPreferences.CurrentCompanyId = 1;
UserSessionPreferences.CurrentFacilityId = 1;
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}

Login only sets the Forms Authentication cookie.
The way asp.net authentication works is that it must read the cookie to set the authenticate the request, but since the cookie did not exist when the Login page was started, the framework doesn't know anything about the user.
Reload the page, and you will find the information is available.
FYI, this is nothing new with SimpleMembership or WebSecurity, this is the way Forms Authentication has always worked.

Related

MVC only alow admin login

I am working on a MVC5 EF6 database in Visual Studio 2013. The database is using Individual User Accounts for authentication with user roles.
In IIS it is possible to get the entire site down (e.g. for maintenance) by putting the app_offline.htm file. After I have updated the site and maybe migrated the database, as administrator I would like to perform some tests on the production machine before allowing all other users to login.
Is there a simple way to lockout all users except administrator from logging in until the administrator allows them to login ?
It would be nice if a similar construction can be used as the app_offline.htm file.
Below the code of the login method.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
I can imagine it would be possible to add some code in case Success to logout the user (if not memeber of administrators) and redirect to a special "logins temporary disabled" page. However, there might be better (or builtin) alternatives.
Edit:
I tried the filtes and they do not work as I expected. However, I will keep filters in mind to test with if I have some more time to do so.
For now I use the following code in the login method
if (!ModelState.IsValid)
{
return View(model);
}
ApplicationUser au = UserManager.FindByName(model.UserName);
// If user is found and not member of administrators
if (au != null && !UserManager.IsInRole(au.Id, "Administrator"))
{
// Check if the adminmode file exisits in the root. If so, redirect to it
String adminModeFilePath = System.IO.Path.Combine(HttpRuntime.AppDomainAppPath, "app_adminmode.htm");
if (System.IO.File.Exists(adminModeFilePath))
{
return new FilePathResult(adminModeFilePath, MediaTypeNames.Text.Html);
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
For "Administrator" and "app_adminmode.htm" I actually use constants which are declared in a central place, but for SO completeness I use the string here. If the user trying to login exists and is not in role Administrator, I will check the file. If the file exists the user is not logged in, but redirected
Maybe using the Authorize Attribute in FilterConfig.cs:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
filters.Add(new AuthorizeAttribute{Roles = "Adminsitrator"});
}
else
{
filters.Add(new AuthorizeAttribute());
}
Edit: Thanks #gavin-coates be sure to Allow Anonymous on the login page or any page that should be accessible by un-uathenticated users.
[AllowAnonymous]
public ActionResult Login() {
// ...
}
Adding a check to see if the user is an admin sounds ok to me.
The only thing I would change though, is to add some kind of boolean flag to enable/disable the restrictions, and place this in your web.config file, so you don't need to modify your code and re-upload it to enable/disable access.
Add the following into your web.config, <appSettings> section:
<add key="SystemLive" value="true" />
Then within your login function, add:
if(!ConfigurationManager.AppSettings["SystemLive"])
{
return View("logins_temporary_disabled_page");
}

MVC give authorization on login to access actions

I've been running in circles trying to find an answer to this, and I can't seem to make any progress.
All I want to do, is check for a correct username and password combo, then GIVE the user authorization to access MVC actions decorated with the [Authorize] tag.
public ActionResult DoLogin(PageInitModel model)
{
EF_db db = new EF_db();
string saltPassword = getSaltedPasswordEncryption(model.UserName, model.Password);
var user = (from s in db.Users
where s.Username == model.UserName
&& s.Password == saltPassword
select s).FirstOrDefault();
if(user == null)
{
model.LoginFail = true;
return View("Login", model);
}
else
{
//
// give the user some magical token to access [Authorize] actions here
//
return RedirectToAction("Index", "Menu");
}
}
Above is the login action (called from a basic form), and below would be one of the actions I would like to restrict access to:
public class MenuController : Controller
{
[Authorize]
public ActionResult Index()
{
var pageInitModel = new PageInitModel();
return View("Menu",pageInitModel);
}
}
I would like to keep track of the users myself (in my own tables), because there are many additional attributes I would like to track. I'm not sure if I need to write a custom AuthorizeAttribute, or what, but I can't seem to make any headway.
Any help pointing me in the right direction is greatly appreciated.
Thanks!
You should look into ASP.NET Identity. Note this was introduced in ASP.NET 5 (?) and replaces some older frameworks Microsoft had like Basic Membership etc.
Honestly you really don't want to roll your own. ASP.NET Identity does exactly what you describe right out of the box. Keep in mind there are two distinct concepts Authentication and Authorization.
Authentication is verifying the user is who he says he is.
Authorization is restricting access to only users who are "allowed".
There are many ways to structure Authorization but I assume Role based Authorization will meet your need. You will define multiple roles, say User, Admin, Moderator, Admin, etc.
You then restrict access to actions based on the role. You can even make roles overlap and allow a single user to have multiple roles. The end result is that once a user logs in their role determines what they can do via the authorize tags.
[Authorize(Roles="Admin")]
If the user is not logged in, they will be redirected to the login form to AUTHENTICATE ("Um I don't know who you are". Once authenticated if they are not authorized they will still be restricted from this action ("Oh I know who you are but you are not allowed to do this".
To round it out you can also have anonymous actions which mean no authentication is required and Authorize actions which are not limited to a specific role (any authenticated user is allowed but not unauthenticated users).
The fact that even with a basic [Authorize] you are having issues leads be to believe there is some configuration problems in even the Authentication. I recommend going through a tutorial building an example app like this one:
http://blogs.msdn.com/b/webdev/archive/2013/10/20/building-a-simple-todo-application-with-asp-net-identity-and-associating-users-with-todoes.aspx
As I understood you have your users information and you may want to use the authentication by custom code. But to actually login the user you must create a session of the user (Cookie session) for further authentication of requests.
You have already verified the username and password. now you've to create a session cookie using form authentication. Here's answer to your first problem:
// give the user some magical token to access [Authorize] actions here
// User name and password matches
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
The Authorize attribute will then check if the request coming for the action is authenticated. This attribute internally checks if a session cookie is valid and authenticated.
Also this attribute not only checks for authentication but also can verify authorization. So you can use custom roles as well with Authorize attribute to restrict users to accessing specific views/actions.
It was pointed out to me that I needed to look into cookies or existing membership providers, so I looked around in FormsAuthenticationTickets and rolled my own solution that I would love feedback on.
I ended up creating a new ticket when the login was successful with the FormsAuthenticationTicket class (see below).
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.Username,
DateTime.Now,
DateTime.Now.AddHours(2),
false,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Then I wrote a custom ActionFilter, so I could make my own method decorators (or whatever they're called).
namespace MyApp.Filters
{
public class CustomLoginFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
// Retrieves the cookie that contains your custom FormsAuthenticationTicket.
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// There's actually a cookie
// Decrypts the FormsAuthenticationTicket that is held in the cookie's .Value property.
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && user != null && data == authTicket.UserData)
{
// Everything looks good
this.OnActionExecuting(filterContext);
}
else
{
//log bounceback - someone screwed with the cookie
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
else
{
//log bounceback - not logged in
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Login" },
{ "action", "Index" }
});
}
}
}
}
I'll likely play with it more, but it seems to work fine for now.
Thanks for the help.

User.Isauthenticated works randomly in ASP mvc

I am trying to make sure that my users log in as a elementryUser .So in Login controller i check the username and password ,if the authentication be true the user can enter the page the code to handle this is given below :(this part of code is login action )
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
System.Threading.Thread.Sleep(10000);
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
As you can see if the username and password be true the cookie is initialized,in this line if (User.IsInRole("ElementryUser")) when i want to check my user permission but it doesn't work and it doesn't execute if statement .so i trace the code and i found that the User.Isauthenticated returns false !!!!why?what is the problem?So i put and thread between these two line because i thought maybe the thread could solve the problem.But it doens't workUser.Isauthenticated returns false and sometimes returns true and when it returns true my if statement works .!!
Best regards
I am answer here general, because I can not do otherwise to your question. I can only give you some guidelines to look, and maybe move forward.
The authentication is connected with one cookie, as you already know.
So if the cookie is not readed then user is not authenticated, beside the fact that can be readed and not authenticate for other reasons.
When a cookie that you have set one one page, can not be readed you can check this reasons:
When you move from https to http pages and the cookie is set only for secure pages.
When you move from example.com to www.example.com to whatevet.example.com
When you set a cookie, but you make a redirect before the cookie have been transmitted to the client.
On web.config you can set up the 1 and 2 on this line.
<authentication mode="Forms">
<forms name=".auth"
path="/"
requireSSL="false"
cookieless="UseCookies"
domain="example.com"
enableCrossAppRedirects="false" />
</authentication>
Set the path, the requireSSL and the domain, without subdomains to set cookie be visible everywhere on your site.
Now if you left the requireSSL="false" the cookie can be possible read by the middle men and login to your site if stolen. Related : Can some hacker steal the cookie from a user and login with that name on a web site?
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); only sets the cookie, it doesn't set IPrincipal in your current request context ("User" is a shortcut to the current requests IPrincipal).
On the next request the user makes to the server, the browser will send the cookie and the FormsAuthentication module will then read that cookie and set IPrincipal on the current request context.
In other words, do something like
public ActionResult Login()
{
... stuff ...
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
return RedirectToAction("someaction");
}
public ActionResult SomeAction()
{
if (User.IsInRole("ElementryUser"))
{
int UserId = objuserrepository.FindBy(i => i.Email == User.Identity.Name).First().Id;
if (firstIdeaRepository.FindBy(i => i.UserId == UserId).Count() > 0)
{
return RedirectToAction("Index", "FirstIdea");
}
}
else
{
return RedirectToAction("Index", "Dashboard");
}
}
This is a problem I have seen in my own apps before. You do end up with an extra roundtrip, but it's only at login so you should be fine.
I suspect the reason you see it sometimes working would have been because you had an authentication cookie already set from your previous test, i.e. you were already logged in before you called FormsAuthentication.SetAuthCookie.
No need of Thread.Sleep();
You can follow this article. You have to use custom authorization filter:
http://www.dotnet-tricks.com/Tutorial/mvc/G54G220114-Custom-Authentication-and-Authorization-in-ASP.NET-MVC.html

change password in MVC 4

I am building ASP.NET MVC 4 application. I use Simple Membership provider to manage authentication and authorization within the system. What are the ways of changing the password in this approach. I found a ChangePassword method which takes three parameters, including original password, to operate.
Is there any other way to override/change the password for the user without actually knowing original password?
ChangePassword is used when a user wants to change their password - and the current password is their evidence to allow this to happen (think Change Password Screen).
I think the most direct way to do this is to call WebSecurity.GeneratePasswordResetToken() and pass the result into WebSecurity.ResetPassword, along with the new password.
var token = WebSecurity.GeneratePasswordResetToken("UserName");
var result = WebSecurity.ResetPassword(token, "NewPassword");
There is a detailed article on how to implement password reset/change with SimpleMembership in MVC 4 here. It also includes source code you can download.
This examples uses email to send a URL to the user to click on for password reset. This is more secure than just having the user enter the old password and new password directly on the website because it is another verification of the user. This alleviates the scenario where someone gets a hold of the user password and locks them out by changing the password. This also allows the user to reset the password in the case where they have forgotten the password.
The code to send the email with the link would look something like this.
[AllowAnonymous]
[HttpPost]
public ActionResult ResetPassword(ResetPasswordModel model)
{
string emailAddress = WebSecurity.GetEmail(model.UserName);
if (!string.IsNullOrEmpty(emailAddress))
{
string confirmationToken =
WebSecurity.GeneratePasswordResetToken(model.UserName);
dynamic email = new Email("ChngPasswordEmail");
email.To = emailAddress;
email.UserName = model.UserName;
email.ConfirmationToken = confirmationToken;
email.Send();
return RedirectToAction("ResetPwStepTwo");
}
return RedirectToAction("InvalidUserName");
}
This creates an email that has a link to a Web API that accepts the token as the id that is passed in. When they click on the link it hits this method.
[AllowAnonymous]
public ActionResult ResetPasswordConfirmation(string Id)
{
ResetPasswordConfirmModel model = new ResetPasswordConfirmModel() { Token = Id };
return View(model);
}
This action gets the token from the query string and puts it in the ResetPasswordConfirmationModel that is passed to the view which allows the user to enter the new password. The new password is entered twice to make sure they entered it correctly, which is validate on the page. When they submit this information they are taken to the POST version of this action which actually resets the password.
[AllowAnonymous]
[HttpPost]
public ActionResult ResetPasswordConfirmation(ResetPasswordConfirmModel model)
{
if (WebSecurity.ResetPassword(model.Token, model.NewPassword))
{
return RedirectToAction("PasswordResetSuccess");
}
return RedirectToAction("PasswordResetFailure");
}

.Net Redirect to User Details after Login / passing the GUID of a User

I am using MVC 3 .Net membership. It's very new to me, and I'm stuck at redirecting the user to their user details page once they have successfully logged in. Here's the Logon Controller. My User details action is expecting the GUID of the User to be passed to it, and I don't know how to do that. I've tried User.id? and Membershipt.GetUser() etc etc but I can't seem to get the GUID of the user that the User Details action needs. Any ideas? Thanks in advance
[HttpPost]
public ActionResult LogOn(LogOnModel 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("Details", "User", new { id = User.??????});
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
All I find with a quick search is problems, rather than solutions or examples. While this doesn't mean anything in and of itself--I'm not familiar with the "AccountModel" and "LogOnModel" namespaces and the related methods. But I could still possibly help.
First question: Where is the GUID stored? Are you using a directory (AD/LDAP) or a database (SQL). The GUID is "in there", and if it is already accessible, wouldn't it be something like model.GUID. Your ValidateUser found the username, but it doesn't "know anything" about the GUID.
ADODB, Linq, and DirectorySearcher all have relatively simple ways of fetching a GUID when you've got a valid username and password, for example.

Resources