I have been scratching my head on this for a while now but still can't get it.
I'm trying to simply log in a user in an MVC2 application.
I have tried everything that I know to try but still can't figure out what I'm doing wrong.
Here are a few things that I have tried:
FormsAuthentication.SetAuthCookie( emailAddress, rememberMe );
var cookie = FormsAuthentication.GetAuthCookie( emailAddress, rememberMe );
HttpContext.Response.Cookies.Add( cookie );
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( emailAddress, rememberMe, 15 );
FormsIdentity identity = new FormsIdentity( ticket );
GenericPrincipal principal = new GenericPrincipal(identity, new string[0]);
HttpContext.User = principal;
I'm not sure if any of this is the right thing to do (as it's not working).
After setting HttpContext.User = principal then Request.IsAuthenticated == true.
However, in Global.asax I have this:
HttpCookie authenCookie = Context.Request.Cookies.Get(
FormsAuthentication.FormsCookieName );
The only cookie that ever is available is the aspnet session cookie.
Any ideas at all would be much appreciated!
You're doing way too much work. It takes one function call to log someone in. Here's the boilerplate code from a new MVC 2 app:
private bool ValidateLogOn(string userName, string password)
{
if (String.IsNullOrEmpty(userName))
{
ModelState.AddModelError("username", "You must specify a username.");
}
if (String.IsNullOrEmpty(password))
{
ModelState.AddModelError("password", "You must specify a password.");
}
if (!MembershipService.ValidateUser(userName, password)) // this is the login
{
ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
}
return ModelState.IsValid;
}
Note the commented line. That's all you need to do a login. I note that you're not calling ValidateUser in your code in the question. You need that.
Related
I'm told to make admin have a functionality to change other users password without knowing their original password. I wrote a code that changes and saves password successfully in database, but when I try to login as that user I can't.
UsersController:
public ActionResult ChangePassword()
{
return View();
}
[HttpPost]
public ActionResult ChangePassword(int id, ViewModels.ChangePasswordViewModel model)
{
if (!SessionControlService.CheckIsLoginStillTrue(_loginsService, HttpContext))
return RedirectToAction("Login", "Account");
if (!User.IsInAnyRoles("Admin", "PropertyManager"))
return RedirectToAction("Error", "Errors",
new { error = Facility.Web.Resources.Resources.ErrorNotHavePermission });
var user = _userService.GetUser(id);
if (user == null)
return RedirectToAction("Error", "Errors",
new { error = Facility.Web.Resources.Resources.ErrorURLNotExist });
user.Password = model.NewPassword;
_userService.UpdateUser(user);
return RedirectToAction("Details", new { id = id });
}
Why can't I use the changed password which is saved in the database to login?
How can I make this work?
In ASP.NET MVC5, password is hashed... you cannot save a plaintext password like that.
You need to use these two methods:
var manager = new ApplicationUserManager(...);
var token = manager.GeneratePasswordResetToken(userId)
manager.ResetPassword(userId, token, newPassword)
You could also try ApplicationUserManager.UpdatePassword(...), or RemovePassword(...) and AddPassword(...)
ApplicationUserManager is normally in IdentityConfig.cs
I am trying to redirect user to page based on their role. Below is the login code in a controller:
[HttpPost]
public ActionResult Login(User model)
{
// Lets first check if the Model is valid or not
if (ModelState.IsValid)
{
using (AuthenticationAppEntities1 entities = new AuthenticationAppEntities1())
{
string username = model.Username;
string password = model.Password;
// Now if our password was enctypted or hashed we would have done the
// same operation on the user entered password here, But for now
// since the password is in plain text lets just authenticate directly
bool userValid = entities.Users.Any(user => user.Username == username && user.Password == password);
// User found in the databases
if (userValid)
{
FormsAuthentication.SetAuthCookie(username, false);
if (Roles.IsUserInRole(model.Roles, "admin"))
{
return RedirectToAction("Home", "Authentication");
}
else
{
return RedirectToAction("HomeAdmin", "Authentication");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
As you can see in the code, I redirect users based on their roles, using a if. However, model.Roles contain a null value as the controller is getting only the username and password from the login page. Any simple idea how I get the role of the authenticated user? I thought of searching it through the username, but am not sure if this is the best solution.
You can get the User's roles and then compare it with 'admin' and then redirect accordingly.
Honestly, I'm not even sure how you're getting that to run in the first place. The method signature for Roles.IsUserInRole is:
Roles.IsUserInRole(string username, string role);
Passing model.Roles is neither a string, nor a username, if it was a string.
in my controller AuthController/signin i have this code:
entities.UserAccount user = (new BLL.GestionUserAccount()).authentifier(email, password);
//storing the userId in a cookie
string roles = (new BLL.GestionUserAccount()).GetUserRoles(user.IdUser);
// Initialize FormsAuthentication, for what it's worth
FormsAuthentication.Initialize();
//
FormsAuthentication.SetAuthCookie(user.IdUser.ToString(), false);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
user.IdUser.ToString(), // Username associated with ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMinutes(30), // Date/time to expire
true, // "true" for a persistent user cookie
roles, // User-data, in this case the roles
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Get the stored user-data, in this case, our roles
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
// Add the cookie to the list for outgoing response
Response.Cookies.Add(cookie);
return RedirectToAction("index", "Home");
in the master page i have a menu ,in that menu there is an item that is meant to be seen only by admin role.
<% if (HttpContext.Current.User.IsInRole("admin")){ %>
<%=Html.ActionLink("Places", "Places", "Places")%>
<%} %>
even with HttpContext.Current.User conatining the right roles,i can't see the item:
globalx asax:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
Instead of using User.IsInRole(), try the static method Roles.IsUserInRole().
I know it sounds silly but from your image I can only see your userData from your ticket.
The only thing I can think if is if the userData is not going into the principal. (Possibly a problem with the last three lines of glabal.asax.cs)
Something is wrong here:
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
You will need a custom Authorize attribute which will parse the user data portion of the authentication ticket and manually create the IPrincipal. Take a look at this post which illustrates the way I would recommend you to do this in ASP.NET MVC. Never use HttpContext.Current in an ASP.NET MVC application. Not even in your views. Use <% if (User.IsInRole("admin")) { %> instead.
One statement is missing.
After this line:
FormsAuthenticationTicket ticket = id.Ticket;
You need to put this line:
ticket = FormsAuthentication.Decrypt(ticket.Name);
In global.asax assign principal on 2 objects like that:
private static void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
I found it here ASP.NET documentation
We use MVC 3. The default user management is not usable for us as our account info is stored in our own data-store and access goes via our own repository classes.
I'm trying to assign a principal add roles to the HttpContext.User and give out an authorization cookie.
Based on a code snipped I found I tried something like this:
if (UserIsOk(name, password))
{
HttpContext.User =
new GenericPrincipal(
new GenericIdentity(name, "Forms"),
new string[] { "Admin" }
);
FormsAuthentication.SetAuthCookie(name, false);
return Redirect(returnUrl);
}
When the next request is done, the user is authenticated, but he is not in the "Admin" role.
What am I missing?
I think you should implement FormsAuthenticationTicket.
More info here : http://msdn.microsoft.com/en-us/library/aa289844(v=vs.71).aspx
In Mvc it is quite similar.
I have a class called UserSession that is injected into LoginController and that I use in LogOn action :
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Index(LoginInput loginInput, string returnUrl)
{
if (ModelState.IsValid)
{
return (ActionResult)_userSession.LogIn(userToLog, loginInput.RememberMe, CheckForLocalUrl(returnUrl), "~/Home");
}
}
Here's my UserSession LogIn implementation (notice I put the "Admin" role hard coded for the example, but you could pass it as argument) :
public object LogIn(User user, bool isPersistent, string returnUrl, string redirectDefault)
{
var authTicket = new FormsAuthenticationTicket(1, user.Username, DateTime.Now, DateTime.Now.AddYears(1), isPersistent, "Admin", FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
if (authTicket.IsPersistent) authCookie.Expires = authTicket.Expiration;
HttpContext.Current.Response.Cookies.Add(authCookie);
if (!String.IsNullOrEmpty(returnUrl))
return new RedirectResult(HttpContext.Current.Server.UrlDecode(returnUrl));
return new RedirectResult(redirectDefault);
}
Then in the base controller I've overriden OnAuthorization method to get the cookie :
if (filterContext.HttpContext.Current.User != null)
{
if (filterContext.HttpContext.Current.User.Identity.IsAuthenticated)
{
if( filterContext.HttpContext.Current.User.Identity is FormsIdentity )
{
FormsIdentity id = filterContext.HttpContext.Current.User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = id.Ticket;
string roles = ticket.UserData;
filterContext.HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
I hope this helps. Let me know.
You sure, that roles are enabled, and there is such role?
If not, do following:
In Visual Studio:
Project -> ASP.NET Configuration
Then choose Security, enable roles. Create role "Admin".
Then try your approach
I've implemented some basic, custom membership provider for my ASP.NET MVC application so I thought that all validation will be done in my custom code.
Unfortunately when I'm trying to create new user by calling function:
Membership.CreateUser(user.UserName, user.Password, user.Email, null, null, true, Guid.NewGuid(), out status);
which should eventually throw an exception with all validation errors I'm getting a status like "InvalidUserName" or "InvalidPassword" instead... That means that my custom CreateUser function isn't call directly, it's used after some basic validation which I would wish to skip.
My CreateUser function (in my custom provider):
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
try
{
User user = new User();
user.UserKey = Guid.NewGuid();
user.UserName = username;
user.passwordSalt = string.Empty;
user.Password = this.TransformPassword(password, ref user.passwordSalt);
user.Email = email;
user.PasswordQuestion = passwordQuestion;
user.PasswordAnswer = passwordAnswer;
user.CreationDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
user.LastLoginDate = DateTime.MinValue;
user.LastPasswordChangeDate = DateTime.Now;
this._UsersRepository.SaveUser(user);
status = MembershipCreateStatus.Success;
return CreateMembershipFromInternalUser(user);
}
catch(RuleException ex)
{
throw ex;
}
}
Do you know how to enforce direct usage of custom CreateUser function !?
But I'm not using a default ASP.NET MVC project's AccountController...
Just take a look:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Register(User user, string password_confirm, bool acceptsTerms)
{
if (!acceptsTerms)
ModelState.AddModelError("acceptsTerms", "Musisz zaakceptować regulamin");
if (ModelState.IsValid)
{
try
{
MembershipCreateStatus status = new MembershipCreateStatus();
Membership.CreateUser(user.UserName, user.Password, user.Email, null, null, true, Guid.NewGuid(), out status);
}
catch (RuleException ex){
ex.CopyToModelState(ModelState,"user");
}
}
return View();
}
The point is that I'm getting a status instead of RuleException ex when user.UserName or user.Password is empty. My custom RuleException ex would give me back such informations as well. Where a status value is assigned right now !? Bacouse it's not done in my implementation of CreateUser...
I am using this (MVC3) and I have no issues:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
MembershipCreateStatus status;
Membership.Provider.CreateUser(model.UserName, model.Password, model.Email, "", "", true, Guid.NewGuid(), out status);
if (status == MembershipCreateStatus.Success)
{
FormsService.SignIn(model.UserName, false);
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", AccountValidation.ErrorCodeToString(status));
}
}
// If we got this far, something failed, redisplay form
ViewBag.PasswordLength = MembershipService.MinPasswordLength;
return View(model);
}
Using ILSpy to view the static method Membership.CreateUser, you will find it performs validation on
Username (trim whitespace + not null, not empty)
Password (trim whitespace + not null, not empty, length checks)
Email (trim whitespace)
Password Question (trim whitespace + not empty)
Password Answer (trim whitespace + not empty)
It then calls the custom provider.
The key here is to not call the static method CreateUser
Membership.CreateUser(...)
Rather, call the custom provider directly by using:
Membership.Provider.CreateUser(...)
Tested and verified working, as of .NET 4
While my site recommends having a password, we support openid. So forcing a user to have a password just seemed counter to what openid is good for.
Complete rewrite
The ASP.NET Membership system always does some initial validation of the inputs when APIs such as CreateUser are called.
I don't know of any way around this aside from not going directly to the ASP.NET membership APIs.