MVC 4 GeneratePasswordResetToken how to generate a new one? - c#

I am using the following code to generate a reset Token
var token = WebSecurity.GeneratePasswordResetToken(user);
it worked the first time, in that it updated the webpages_Membership table, and updated the following fields,
PasswordVerificationToken
PasswordVerificationTokenExpirationDate
but when I run the above code again, the token returned is the same, and nothing is updated.
how does it work?

The token will only refresh once it has been used with WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);. If you do not complete the full password reset process, the same token will always be generated for that specific user.
If the password is not reset within 24 hours (default), then the above method will return false. If you want to override the default password expiration, you can add the optional parameter tokenExpirationInMinutesFromNow when calling WebSecurity.GeneratePasswordResetToken :
public static string GeneratePasswordResetToken(
string userName,
int tokenExpirationInMinutesFromNow
)
If you want to catch a bad password change attempt, just wrap WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword); in a try catch block, and you'll be able to display an error to the user :
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordModel model)
{
if (ModelState.IsValid)
{
try
{
//Reset password using the reset token and the new password
WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);
//Redirect to the home account page.
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, LocalizedText.Account_Reset_Password_Error);
}
}
//Something bad happen, notify the user
return View(model);
}
I won't worry to much about catching a specific exception here, because the fix will be the same either way - they'll need to generate another password reset token.

Related

"Cannot redirect after http headers have been sent", but Response.Redirect doesn't work if I remove RedirectToAction

So I'm trying to recycle some code that was for a 'code behind' patterned .NET app for my MVC app and the Authenticate class they used. How the SignInController's Index method based on the code they gave me is supposed to work is to call an Authenticate class method in the else if which gets a token and redirects back to the Index method at which point since the app now has a token, it goes into the first if conditional and a different method in the aforementioned Authenticate validates the token. Since users will not start out with a token, the else if will always be dove into first.
In order to soothe the "Not all code paths return a value" error I have to add a return statement at the end of the else if clause and an else clause. However, if I return null Index doesn't get redirected to as confirmed by breakpoints. However, if I do return RedirectToAction("Index", "SignIn"); I get an error about "Cannot redirect after HTTP headers have been sent" which I suspect is because the Redirect call from the Authenticate class hasn't been completed yet. However I'm at odds as to how to remedy situation as either return value fails to redirect the web app back to Index...
Original "look behind" styled .NET code that I'm trying to recycle from a colleague's app:
if (string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name) && HttpContext.Current.Request.QueryString["Token"] != null)
{
// we’ve got a token, they must have logged in .. double-check the token
string ssoToken = HttpContext.Current.Request.QueryString["Token"].ToString();
string userRoles = string.Empty;
if (Authenticate.ValidateSSOToken(ssoToken, out userRoles))
{
string userName = HttpContext.Current.User.Identity.Name;
((BaseApplicationPage)(this.Page)).CurrentSecurity.SetUser(userName, "", userRoles);
RedirectOnSuccess();
}
else
{
RedirectToForbiddenPage();
}
}
else if(string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name))
{
// no user data..go ask them to get SSOToken from service
Authenticate.isUserAuthenticated();
}
My attempt to repurpose it into a MVC styled .NET app:
public ActionResult Index()
{
if (string.IsNullOrEmpty(System.Web.HttpContext.Current.User.Identity.Name) && System.Web.HttpContext.Current.Request.QueryString["Token"] != null)
{
// we’ve got a token, they must have logged in ... double-check the token
string ssoToken = System.Web.HttpContext.Current.Request.QueryString["Token"].ToString();
string userRoles = string.Empty;
if (Authenticate.ValidateSSOToken(ssoToken, out userRoles))
{
string userName = System.Web.HttpContext.Current.User.Identity.Name;
//((BaseApplicationPage)(this.Page)).CurrentSecurity.SetUser(userName, "", userRoles);
//RedirectOnSuccess();
// TODO: Not sure what the MVC equivalent would be for commented out code above
return RedirectToAction("Index", "Checklist");
}
else
{
//RedirectToForbiddenPage();
HttpStatusCodeResult(HttpStatusCode.Forbidden);
}
}
else if (string.IsNullOrEmpty(System.Web.HttpContext.Current.User.Identity.Name))
{
// no user data...go ask them to get SSOToken from service
Authenticate.isUserAuthenticated();
return null; // Screwed if I don't return anything because of build error, screwed if I do return something because it messes with the redirect
}
else
{
return null;
}
}
Authenticate class snippet at the end of isUserAuthenticated that gets the token:
//string RedirectURL = GetBaseVirtualDirectory() + "/SignIn/Index";
string RedirectURL = "https://localhost:XXXX1/SignIn/Index";
HttpContext.Current.Response.Redirect(authServiceURL + "/Windows/Auth?RedirectURL=" + RedirectURL, true);
The problem is that your Authenticate.ValidateSSOToken method already called HttpContext.Current.Response.Redirect, which, as error message confirms, added a redirect header (Location) to the response.
You might be able to clear the response before calling RedirectToAction.
But a method called ValidateSSOToke probably should not do any redirects itself. It should return a status and you should do any redirects outside of it based on that status.
And doing all that validation inside your Action is probably not a good practice to begin with.

Retrieving T from Task<T>

I'm working on ASP.NET MVC5 app based around Parse.com framework.
Since i can't use Parse login method i had to use method posted here to work around its limitations: Parse.com multiple users issue
Here is my login method(just minor changes):
public async Task<ActionResult> Login(AccountModel model) //no returnUrl string
{
ParseUser user;
try
{
user = await ParseUser.LogInAsync(model.UserName, model.Password);//login parse user
}
catch (Exception e)
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View(model);
}
//making setAuthCookie get parse object id instead of username
FormsAuthentication.SetAuthCookie(user.ObjectId, model.RememberMe);
ParseUser.LogOut(); //log out parse user
return RedirectToAction("index", "home"); //Redirect to Action
}
So basically i (parse)login user, set AuthCookie to it's object id and then (parse)logoff user. That way i can have multiple users logged in.Out of SetAuthCookie i can get users id now.
However i'd like to display some extra user data(like user adress, Type, Name, LastName) that is on parse.com cloud. So i figured i will just write a method that will get this data by using currently authenticated userID, fill my AccountModel class object with data and then pass it to views. This is a loose idea of how it'd look like(i know syntax is probably wrong, i don't have access to my Visual studio right now):
UserData model:
public async Task<AccountModel> GetUserData()
{
AccountModel userData = new AccountModel();
ParseObject user;
ParseQuery<ParseObject> query = ParseObject.GetQuery("_User");
try
{
//i can't remember how to get authenticated user identity
user = await query.GetAsync(AuthenticatedUser.Identity());
}
catch(Exception e)
{
//code to handle exception
}
userData.Name = user.Get<string>("Name");
userData.Lastname = user.Get<string>("Lastname");
userData.Adress = user.Get<string>("Adress");
return userData; //it will probably throw an error
}
Controller:
public ActionResult Index()
{
UserData model = new UserData();
return View(model.GetUserData());
}
So now it will probably throw an error(can't return T from Task< T >) and i have no idea how to fix this, so i can get currently logged in user data.
I have nav bar on my site where user name and last name is displayed, so i have to somehow get currently logged in user data every time page is displayed. Is there any work around/easier way to achieve this?
You fix this by making your Action asynchronous as well:
public async Task<ActionResult> Index()
{
UserData model = new UserData();
return View(await model.GetUserData());
}
Async goes "all the way". This means that once you have an asynchronous method that needs to be awaited, it will usually cause most (if not all) of your stack-trace to be asynchronous as well.
Side note:
Once should stick to .NET conventions and mark async methods with the XXXAsync postfix, so your method should actually be named GetUserDataAsync.

How to find OldPassword to let the user change it

I'm working on an intranet, I've just added a feature on the user's profile to change his password.
As you can see with the following controller :
[HttpPost]
public ActionResult ChangePassword(Employee objToEdit, FormCollection form, LocalPasswordModel model) // Find how to obtain "OldPassword" from AccountModel
{
objToEdit.Login = User.Identity.Name;
string name = objToEdit.FirstName;
string pwd = form["NewPassword"];
string confirm = form["ConfirmPassword"];
if (_service.Edit_password(objToEdit, pwd, confirm)) // Checks if NewPassword and ConfirmPassword are the same, and does some syntax checking
{
bool changePasswordSucceeded;
try
{
changePasswordSucceeded = WebSecurity.ResetPassword(WebSecurity.GeneratePasswordResetToken(objToEdit.Login), pwd); // Seems to work
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Index", new { Message = CRAWebSiteMVC.Controllers.AccountController.ManageMessageId.ChangePasswordSuccess });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
return new RedirectResult(Url.Action("Index"));
}
return View();
}
So far, the user just needs to input a New password and a confirmation password. I wish to add a "Enter your current Password" feature but I can't find a way to retrieve the user's current password !
The user profile DB does not contain a Password column anymore and I use Form authentication if that's of any help.
EDIT: Thank you for your help, to solve my problem I simply replaced the ResetPassword line by the following :
changePasswordSucceeded = WebSecurity.ChangePassword(objToEdit.Login, current, pwd);
If it fails, it directly displays the error message that the current password is wrong.
You can't !
That's actually a security feature. You should never store a password in plain text.
The good thing is, you don't need to do the comparison yourself:
Instead, use something like ValidateUser to let the Membership Provider validate the provided password. Behind the scenes, this method will hash the password and compare it with the hashed version contained in the database.
EDIT:
Also, note that since you are using the WebSecurity class, there is a method, ChangePassword that accepts the current password. It seems that method will check the current password matches the specified currentPassword parameter. Maybe you should use this one instead of ResetPassword

How to get user information after successful authentication?

The goal
Get the user information after successful authentication.
The problem
Take a look in the following fragment of code:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[AllowAnonymous]
public ActionResult Authenticate(User userModel)
{
if (ModelState.IsValid)
{
if (userModel.IsValid(userModel.Email, userModel.Password))
{
FormsAuthentication
.SetAuthCookie(userModel.Email, userModel.Remember);
return RedirectToAction("Index", "Manager");
}
else
{
ModelState.AddModelError("", "Login data is incorrect!");
}
}
return RedirectToAction("Index");
}
As you can see, it is a normal authentication's controller. What I need is simple: if the statement userModel.IsValid is true, how can I get the user information based on the email that he sent to the server by userModel.Email?
Maybe store the email on the session and in the Index method call some method to get the (user) information passing through parameter the email that inhabiting the session? (I think this isn't the best way because if the cookie exist and the session not, there will be a problem here.)
Code spotlight
To get information of some user, I'm using a simple method: User.Invoke((string) userEmail).
Knowledge improvement
I'm logging in on my website with email and password as various applications of the world do. With the email that the user enters, I'm attempting to get his information from database. So I ask: is this the best way to do this? Maybe isn't better firstly get the ID of the user by his email and then select his information?
What I already tried
In the Authenticate method (the same that I passed before), I implemented the following code:
[...]
public ActionResult Authenticate(User userModel)
[...]
if (userModel.IsValid(userModel, userModel.Password))
{
FormsAuthentication
.SetAuthCookie(userModel.Email, userModel.Remember);
Session["UserEmail"] = userModel.Email; // <-- Pay attention to this
return RedirectToAction("Index", "Manager");
}
[...]
}
And then, in the Index method:
public ActionResult Index()
{
if(Request.IsAuthenticated())
{
UserProfile user = User.Invoke(Session["UserEmail"]));
return View(user);
}
else
{
return View();
}
}
But as I said, if the cookie that flags that the user is logged in is alive and the session not, there will be a problem right here — a kind of concept conflict (cookie vs. session).
What can I do?
The accepted answer (from the discussion with the OP) is : the most straightforward way of retrieving the user name set by the forms authentication module is to use
HttpContext.Current.User.Identity.Name
or just
this.User.Identity.Name
in a controller.

How to stop MembershipService.CreateUser() from auto login on ASP.NET MVC?

I created an App with ASP.NET MVC 1.0 and wish to use a custom method (for admins) to create a user. I took the Register method (in the Account controller) and renamed it to Create. I then commented out the line FormsAuth.SignIn(userName, false); to avoid the newly created user to sign in.
When I complete the create user form, the user gets added fine, but he also gets signed in. Now both me and the new user are signed in. I know this because my ListUsers page tests for user.IsOnline
UPDATE (2009-07-15 14:40): I have been doing some Google-ing and found that User.IsOnline is not very reliable due to the stateless HTTP protocol. Note: if I go to the UserDetails page (which is populated using MembershipUserAndRolesViewData) the Last Login shows as NULL. But my ListUsers page shows a login date...???
public class AccountController : Controller
{
// ...
[Authorize(Roles = "Administrator")]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string userName, string email, string password, string confirmPassword)
{
ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
if (ValidateRegistration(userName, email, password, confirmPassword))
{
// Attempt to register the user
MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);
if (createStatus == MembershipCreateStatus.Success)
{
//FormsAuth.SignIn(userName, false); // createPersistentCookie
return RedirectToAction("ListUsers", "Account");
}
else
{
ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
}
}
// If we got this far, something failed, redisplay form
return View();
}
}
Checking http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.isonline.aspx mentions this:
A user is considered online if the current date and time minus the UserIsOnlineTimeWindow property value is earlier than the LastActivityDate for the user.
The LastActivityDate for a user is updated to the current date and time by the CreateUser, UpdateUser and ValidateUser methods, and can be updated by some of the overloads of the GetUser method.
This page http://msdn.microsoft.com/en-us/library/system.web.security.membershipuser.lastactivitydate.aspx also says this:
The LastActivityDate for a user is updated to the current date and time by the CreateUser and ValidateUser methods, and can be updated by some overloads of the GetUser method. You can use the UpdateUser method to set the LastActivityDate property to a specific date and time.
So it seems that when you create a new account, this is considered as being "Online".
A workaround could be to modify the default CreateUser in the AccountMembershipService class to reset the date when you create an account:
public MembershipCreateStatus CreateUser(string userName, string password, string email)
{
MembershipCreateStatus status;
MembershipUser user = _provider.CreateUser(userName, password, email, null, null, true, null, out status);
user.LastActivityDate = DateTime.MinValue; //set the LastActivityDate to a point far back in the past
_provider.UpdateUser(user); //update the user to the MembershipProvider
return status;
}
There must be something wrong with a code you didn't show. If you create a new ASP.NET MVC project using the default VS template and then comment out the line FormsAuth.SignIn(userName, false) the user is not signed in as expected.
It's a bit of a hack, but I suppose you could log them out before redirecting them.
FormsAuth.SignOut();
return RedirectToAction("Index", "Home");

Categories