Custom login with mvc messing up with the login partial view - asp.net-mvc

So I've set up a custom login in my MVC application which appears to work...
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
if (ModelState.IsValid)
{
if (model.Login())
{
FormsAuthentication.SetAuthCookie(model.Username, true);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Invalid email address or password.");
}
}
// If execution got this far, something failed, redisplay form
return View(model);
}
public class User
{
public bool Login()
{
var user = db.Users.FirstOrDefault(u => u.EmailAddress == EmailAddress);
if (user == null)
{
throw new ValidationException("User not found.");
}
else
{
// validates whether or not the password on the user record
// that was retrieved by the query matches the password entered at login
return Hashing.ValidatePassword(Password, user.Password);
}
}
}
Unfortunately, there's some conflict between it and the default _LoginPartial.cshtml View (which looks like below):
#model LoganMVC.Models.User
#if (Request.IsAuthenticated) {
<text>
Hello, #Html.ActionLink(User.Identity.Name, "Manage", "Account", new { User.Identity.Name })!
#using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm" })) {
#Html.AntiForgeryToken()
Log off
}
</text>
} else {
<ul>
<li>#Html.ActionLink("Register", "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
<li>#Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
</ul>
}
The ArgumentNullException highlights this line...
Hello, #Html.ActionLink(User.Identity.Name, "Manage", "Account", new { User.Identity.Name })!
Saying...
Value cannot be null or empty.
It's clear that the value that cannot be null or empty is User.Identity.Name, but what isn't clear is WHY IsAuthenticated is true to begin with. This is Forms Authentication and, as far as the software is aware (because I killed the debug server, closed the solution and started debugging fresh again), the application has never run before.

Okay I've been bashing my head against various hard surfaces trying to get this one figured out and eventually did.
After looking at the errors I was initially getting:
Value cannot be null or empty.
And then later
The provided identity of type 'System.Web.Security.FormsIdentity' is marked IsAuthenticated = true but does not have a value for Name. By default, the anti-forgery system requires that all authenticated identities have a unique Name. If it is not possible to provide a unique Name for this identity, consider setting the static property AntiForgeryConfig.AdditionalDataProvider to an instance of a type that can provide some form of unique identifier for the current user.
Were fairly simple fixes.
First off, I had initially set my login cookie to persist. This means that it wouldn't be deleted when the browser is closed and would not expire either. While this did not cause the Value cannot be null or empty error, it was contributory in that the site was finding it and assuming a user had authenticated.
FIX
To fix this, I simply cleared cookies from my browser set the cookie's persist to false. This allowed me to focus more on the actual problem.
Value cannot be null or empty
This problem was actually not as complicated as I thought. When I started taking a closer look at things, I noticed that there was an error in the following code:
[HttpPost]
public ActionResult Login(User model, string returnUrl)
{
if (ModelState.IsValid)
{
if (model.Login())
{
FormsAuthentication.SetAuthCookie(model.Username, true);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "Invalid email address or password.");
}
}
// If execution got this far, something failed, redisplay form
return View(model);
}
This post captures an instance of my User model from the form (which is the login form so only EmailAddress and Password values are included). It also uses the Username value from this model (which is null) to set the Authentication Cookie.
I changed my .Login() method to a function that returns a User if the email address is found and the stored hash matches a hash of the password entered for the login.
The code, thus, changed to the following (this is only the key point of the change)
// Account.Login Post
if (ModelState.IsValid)
{
var user = model.Login(model.EmailAddress);
if (user == null)
{
ModelState.AddModelError("", "Invalid email address or password.");
}
FormsAuthentication.SetAuthCookie(user.Username, true);
// create session variables n such
}
// User.Login
public User Login(string EmailAddress
{
var user = dbContext.Users.FirstOrDefault(u => u.EmailAddress == Email);
if (user == null)
throw new Exception("User not found.");
if (Hashing.ValidatePassword(Password, user.Password))
return user;
return null; // if we got here, something went wrong
}
Once I'd fixed this, I got another problem later. Some issue to do with the Anti-Forgery system.
While not a solution, I managed a workaround and commented out the following line from my _LoginPartial View:
#Html.AntiForgeryToken()
I had concerns about security but I figured that Forms Auth on a basic asp.net website with a hashed and salted password has been sufficient for quite a number of years, so I shouldn't have any issues with it here.

You probably miss something. In your controller Account you have an Action:
public ActionResult Manage(string name) // I presume name
{
....
}
Then your line should be:
Hello, #Html.ActionLink(User.Identity.Name, "Manage", "Account", new { name=User.Identity.Name })!
The anonymous object which is routeValues
It's n object that contains the parameters for a route. The parameters are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.(Definition taken from MSDN) And it should be well formatted.
I hope it will solve the problem otherwise you have to check if the User.Identity is different of null.
I have noticed also somthing else in this line:
var user = db.Users.FirstOrDefault(u => u.EmailAddress == EmailAddress);
Where comes from the EmailAddress after ==?
You should provide a parameter to you Login method to replace your second EmailAddress.
public bool Login(string emailAddress)
{
var user = db.Users.FirstOrDefault(u => u.EmailAddress == emailAddress);
if (user == null)
{
throw new ValidationException("User not found.");
}
else
{
// validates whether or not the password on the user record
// that was retrieved by the query matches the password entered at login
return Hashing.ValidatePassword(Password, user.Password);
}

Related

I'm validating the credentials of a user but this function is always evaluated as true

I'm validating the credentials of a user but this function is always evaluated as true
public ActionResult Authorize(string emai, string pass)
{
using (WorkFlowContext db = new WorkFlowContext())
{
var email = db.User.Where(x => x.Email_Address == emai).FirstOrDefault();
var password = db.User.Where(x => x.Password == pass).FirstOrDefault();
//if one of these if true it will send a menssage asking for the email or the password again
if ((email == null) || (password == null))
{
ViewBag.Message = "The email or password is incorrect.";
return View("Index");
}
else {
return RedirectToAction("About", "Home");
}
}
// return RedirectToAction("Index", "Login");
}
this part is the form from where I'm getting the values
As #the_lotus has mentioned, the logic there is (dangerously) wrong. You're saying get me ANY user with this password AND ANY user with this email address...which could be > 1 users.
You should try something like this:
using (WorkFlowContext db = new WorkFlowContext())
{
var user = db.User.Where(x => x.Email_Address == email).FirstOrDefault();
if(user!=null) {
if(user.Password == pass) {
//all ok - send to home (see my notes below though)
return RedirectToAction("About", "Home");
}
}
}
//if we get here - something went wrong
ViewBag.Message = "The email or password is incorrect.";
return View("Index");
...other notes
Look up ASP.NET Identity - it's a really good system with lots of security features available out of the box covering lots of things you may not think of.
Don't store your password as plain text
Be ambigous in your error message - no need to give too much away like "we found the email in our database but the password isn't right"

MVC Email Confirmation (Sequence contains more than one element)

I'm currently trying to add email confirmation to my website and i'm experiencing a few problems.
I can succesfully register an account, upon registration the confirmationToken gets placed in my database and a email gets send with the query string link: http://www.example.com/RegistrationConfirmation?9ZPwZZrO-UmdpVpxXWjmRw when going to this link the controller action RegistrationConfirmation gets called and the method ConfirmAccount does a query to see if we can find a user with the confirmation token that was passed in the url.
When debugging I get the error "Sequence contains more than one element" on this line: Account user = context.Accounts.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
I'm not sure what's going wrong cause the token is unique and there are no duplicate tokens in the database.
Register HttpPost:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterModel model)
{
string confirmationToken = CreateToken();
Account account = new Account(model.Username, model.Password, model.FirstName, model.LastName, model.Email, false, confirmationToken);
if (DatabaseHandler.isUsernameDuplicate(account.Username))
{
// is duplicate // provide notification
}
else
{
Session["accountID"] = Repository.InsertAccount(new Account(model.Username, model.Password, model.FirstName, model.LastName, model.Email,false, confirmationToken));
// Email Logic
try
{
await client.SendMailAsync(message);
}
catch (Exception e)
{
ModelState.AddModelError("", "Problem sending email: " + e.Message);
}
return View("ConfirmEmail");
}
return View();
}
RegistrationConfirmation HttpGet:
[HttpGet]
[AllowAnonymous]
public ActionResult RegisterConfirmation(string Id)
{
if (ConfirmAccount(Id))
{
return RedirectToAction("ConfirmationSuccess");
}
return RedirectToAction("ConfirmationFailure");
}
ConfirmAccount method:
private bool ConfirmAccount(string confirmationToken)
{
RecipeDbContext context = new RecipeDbContext();
Account user = context.Accounts.SingleOrDefault(u => u.ConfirmationToken == confirmationToken);
if (user != null)
{
user.IsConfirmed = true;
DbSet<Account> dbSet = context.Set<Account>();
dbSet.Attach(user);
context.Entry(user).State = EntityState.Modified;
context.SaveChanges();
return true;
}
return false;
}
Remove SingleorDefault() and check the result.. you will get to know its not duplicate and if you want to avoid this error then use FirstorDefault().
Turned out nothing was wrong with the query itself, the RegisterConfirmation HttpGet did not seem to get the confirmToken value so I added "Id = Request.QueryString.ToString();" and everything is working perfect now :)

ASP.NET MVC 5 Identity 2.0 Set Initial User Password

I've built an MVC 5 website. Adding a user generates and sends an email with a confirmation token. When the user clicks the emailed link, the site responds, asking the user to set an initial password.
When the user tries to set an initial password, the user has not logged in yet, so User.Identity.GetUserId() is null. How do I pass the user ID from the confirmation link to the call to SetPassword()?
AccountController.cs
// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
return View("Error");
var result = await UserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
using (var s = new UserStore())
{
var u = s.FindByIdAsync(userId).Result;
await s.SetEmailConfirmedAsync(u, true);
return View("ConfirmEmail", new { Id = userId }); // This doesn't seem to make Id available.
}
else
return View("Error");
}
ConfirmEmail.cshtml
#{
ViewBag.Title = "Confirm Email";
}
<h2>#ViewBag.Title.</h2>
<div>
<p>
Thank you for confirming your email.
Please #Html.ActionLink("click here to create a password.",
"SetPassword", "Manage",
routeValues: null, // I've tried passing new { Id = Model.Id }, but Id isn't available at run-time.
htmlAttributes: new { id = "loginLink" })
</p>
</div>
ManageController.cs
// POST: /Manage/SetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SetPassword(SetPasswordViewModel model)
{
if (ModelState.IsValid)
{
var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
}
return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess });
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
A simple fix is to add a parameter to the SetPassword method:
public async Task<ActionResult> SetPassword(SetPasswordViewModel model, Guid userIdentifier)
The ConfirmEmail.cshtml would then change to:
Please #Html.ActionLink("click here to create a password.",
"SetPassword", "Manage",
routeValues: null, // I've tried passing new { Id = Model.Id }, but Id isn't available at run-time.
htmlAttributes: new { id = "loginLink", userIdentifier = Model.ID })
When the user clicks the link, you should now have the userIdentifier filled in with the ID of the user the email was sent to.
It's not super secure as they can pass any Guid (if they can figure out which one to send).
You may want to track a PasswordRequest object by ID (instead of userIdentifier) that can expire the email in which case they need to request a new one. This way once the PasswordRequest record is used, it can be deleted to prevent reuse as well.
Note: PasswordRequest is just a made up name.
You collect the password when you first create a user so that you don't need an id for your UserManager.CreateAsync(). You'd send the confirmation email token in the register action after you've created a new row (and generated the id).
The SetPassword() action is for an existing and authenticated user to change their password. You should not mark that as [AllowAnonymous].
you can use the query string, in the confirmation token you can encrypt the username or userid and when you reach the reset page decrypt it and set the Identity to whoever it was.
Edit: In addition to that you can create a new table in the database InitialSetup, with columns user, EncryptedId(guid), Initial.
When someone clicks the email link they will need to input username, and new password. That is when you will check if the input username is the same as the EncryptedId(guid).

How to return an error if UserName Password does not match in MVC 4?

i am new to MVC, i want to create a simple Login Control. I have write the below code in controller:
public ActionResult LoginControl(String UserName, String Password)
{
string result = "";
if (UserName == "ram" && Password == "ram123")
{
result = "1";
}
if (result == "1")
{
return View("LoginControl");
}
else
{
return View("Index", "Login");
}
}
Now what i want to do is: if the UserName and password does not match, it will show me error that "UserName or password does not matched or user does not exists.", please help me how can i do it.
You could add an error to the ModelState:
else
{
ModelState.AddModelError("", "Invalid username or password");
return View("Index", "Login");
}
and then inside the corresponding view use the ValidationSummary helper to display the error:
#Html.ValidationSummary()
You could pass your result to the view and than display there.
To do this create a TempData key and pass your result there and in the view you can get the value of the tempdata key and than show there.
Here is an example on how to use it: ViewBag, ViewData and TempData

ASP.NET MVC Custom Membership Provider "CreateUser"

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.

Resources