UserManager.FindByEmail() returning Null - asp.net-mvc

I'm trying to implement email address based usernames in AspNet.Identity for MVC5. My application works find as long as there is a registered email/username on the system.
I just discovered that if the user does not exist and tries to login an exception gets thrown on Line 72.
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Source Error:
Line 71: //Add this to check if the email was confirmed.
Line 72: var userid = UserManager.FindByEmail(model.Email).Id;
Here is my code.
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
//Add this to check if the email was confirmed.
var userid = UserManager.FindByEmail(model.Email).Id;
// **Line 72. 'userid' is empty.**
// Here is my attempt but doesn't do anything.
if (string.IsNullOrEmpty(userid)) {
ModelState.AddModelError("","You are not registered!");
}
if (!UserManager.IsEmailConfirmed(userid))
{
ModelState.AddModelError("", "E-mail address has not been confirmed.");
return View(model);
}
}
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Thank you!

I tried the code below and worked for me:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
string userName = ""; // to be used as arg to PasswordSignInAsync
// you'd better declare the appropriate Type.
// using "var" doesn't work here - I don't know why...
ApplicationUser user = await UserManager.FindByEmailAsync(model.UserName);
if (user != null)
{
// found an existing user having the given Email
// so let's get it's UserName to test SignIn
userName = user.UserName;
// just for debugging - check your AspNetUser table
// ModelState.AddModelError("", userName + " ID = " + user.Id.ToString());
}
else
{
// Hum... no email found - perhaps the user is really trying the UserName
// Perhaps Email != UserName
// It's interesting to give the user the option to use Email or UserName
// to get authenticated.
// Let's play, then!
userName = model.UserName;
}
// 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(userName, model.Password,
model.RememberMe, shouldLockout: true);
// from here on, it's the scaffolded code...
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);
}
}
Hope it's useful, even after all that time.
Please, let us know if your problem was solved.
Regards.

I added the code below and it works but I still don't understand how FindByNameAsyn() method actually work as opposed to FindByName()? Also
Is there a better way to do this? Thank you!
// Code that works.
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}

Related

Client Login asp.net mvc error model , How return error model to Index View

I use identity in mvc project for Authentication, I have two kind of user,
the first one is Admin user has an Login page , and the second is Client user
the Client user login form used in the layout in model , I made partial view
for Client Login and impeded it in the Layout, When I press Submit button and the data dose not exist( there is no user ) it Should return model message error for user like this
ModelState.AddModelError("", "Invalid login attempt.");
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
the problem is it return me to Partial view that has no layout
How can I return model Error to page that like Home/Index and view model error
this is the Action I post
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ClientLogin(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", "Invalid login attempt.");
return PartialView("~/Views/Account/_LoginPartial.cshtml", model);
//return PartialView("",model);
}
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ModelState.AddModelError("", "You need to confirm your email.");
return View(model);
}
var lang = RouteData.Values["lang"].ToString().ToLower();
if (string.IsNullOrEmpty(returnUrl))
returnUrl = "/" + lang + "/ControlPanel/Home";
// 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.Email, model.Password, model.RememberMe, shouldLockout: false);
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);
}
}

Authorized sections of .net web app are accessible prior to confirmation email being clicked

I'm using .net 4.5.2 and sendgrid. I've used the link below as a guide but rather than using sendgrid v2 i'm using sendgrid v3.
https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
The email confirmation works with a link sent to the registered users email address. When that link is clicked the "Email Confirmation" field in AspNetUsers goes from false to true.
But when the user first submits the registration form - and prior to clicking the confirm email - they become logged into the system. Somehow _LoginPartial is being invoked because the users email address and logoff end up being at the top of the navbar.
So after thinking about it a bit the login action in ActionController is obviously being called just after registration but before email confirmation is clicked. That's not in the Microsoft doc i don't think.
But any advice to fix that would be great. I could check the AspNetUser table for EmailConfirmation == false. But is there a right place to do that?
I checked out this post Prevent login when EmailConfirmed is false and commented out the default login action code and replaced it with this below but it didn't seem to make a difference.
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
//Add this to check if the email was confirmed.
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ModelState.AddModelError("", "You need to confirm your email.");
return View(model);
}
if (await UserManager.IsLockedOutAsync(user.Id))
{
return View("Lockout");
}
if (await UserManager.CheckPasswordAsync(user, model.Password))
{
// Uncomment to enable lockout when password login fails
//await UserManager.ResetAccessFailedCountAsync(user.Id);
return await LoginCommon(user, model.RememberMe, returnUrl);
}
else
{
// Uncomment to enable lockout when password login fails
//await UserManager.AccessFailedAsync(user.Id);
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
The register action:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser {UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
// Send an email with this link
/*These bottom three lines were commented out */
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking ");
return RedirectToAction("ConfirmRegistration");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Login action:
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.Email, model.Password, model.RememberMe, shouldLockout: false);
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);
}
}
In your Register action, comment/remove the line:
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
That will disable the automatic sign in upon registering. Then, in your Login action, add the following code after the initial ModelState.IsValid check, to check if the user's email has been confirmed:
var user = await UserManager.FindByEmailAsync(model.Email);
if (user != null && !await UserManager.IsEmailConfirmedAsync(user.Id))
{
ModelState.AddModelError("", "Please confirm your email address before signing in.");
return View(model);
}

Logging in with un registered email address throws exception

I'm having a little logic problem logging into the site. If there is no registered email address I want validation on the page to fail and show a message under the email address field that says something like "Email address doesn't exist" or something to that nature.
But I'm not sure how this code below for login should take care of that...
In this code below 'user' is null when no email address is found so PasswordSignInAsync throws an exception because the user.UserName property is null.
How would I handle the validation for this and shouldn't this be already built into the logic for the ASP.Net MVC template with single sign on I created?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByEmailAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
// Uncomment to debug locally
// ViewBag.Link = callbackUrl;
ViewBag.errorMessage = "You must have a confirmed email to log on. "
+ "The confirmation token has been resent to your email account.";
return View("Error");
}
}
// 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(user.UserName, model.Password, model.RememberMe, shouldLockout: false);
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);
}
}
If you want that the message "Invalid login attempt" is shown as validation error for the e-mail address field, you have to set the ModelError for the specific property of your viewModel which is holding the e-mail address.
So it should look like this, whereas the "EmailAdress" is the name of the viewModel property:
ModelState.AddModelError("EmailAddress", "Invalid login attempt.");

ASP.NET MVC Login and Redirect Based On Role

I am using the default Login method generated by ASP.NET MVC and wanted to change it so that it will redirect to a specified View based on the user's role. I have checked that the user is in that role. I made the redirect inside the SignInStatus success block with no success.
I use the User.IsInRole() in other blocks of code and works fine. I think the user is not fully logged in when the if statements are executed. I think this is the case, but I am not sure what work around I can implement.
Here is my code below.
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateCustomAntiForgeryTokenAttribute]
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.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
if (User.IsInRole("Customer"))
{
return RedirectToAction("Customer", "Home");
}
else if (User.IsInRole("Requestor"))
{
return RedirectToAction("Caterer", "Home");
}
else if (User.IsInRole("Admin"))
{
return RedirectToAction("Admin", "Home");
}
else
{
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);
}
}
Thanks to #stephen.vakil's link I managed to get it to work by changing the block of code inside the SignInStatus.Success case.
case SignInStatus.Success:
var user = await UserManager.FindAsync(model.Email, model.Password);
var roles = await UserManager.GetRolesAsync(user.Id);
if (roles.Contains("Customer"))
{
return RedirectToAction("Customer", "Home");
}
else if (roles.Contains("Requestor"))
{
return RedirectToAction("Caterer", "Home");
}
else if (roles.Contains("Admin"))
{
return RedirectToAction("Admin", "Home");
}
else
{
return RedirectToLocal(returnUrl);
}
......
You are correct. The User object you're referencing here gets set by the "Authentication" step in the ASP.NET pipeline. For more information about this, check out Lifecycle of an ASP.NET 5 Application
PasswordSignInAsync only validates your user and sets up the authentication cookie for future requests. It doesn't affect the User object, which will still represent an unauthenticated state as fed through the pipeline.
A simple way to achieve what you want is to have your Login method redirect to another action (something like RedirectUser) which then performs Role-based routing. This method will have full access to the authenticated User object and the IsInRole method.
Or, you could implement your own User.IsInRole method that queries your DB directly.
[HttpPost]
public async Task<IActionResult> SignIn([FromForm]LoginDto userDto, string returnUrl)
{
if (ModelState.IsValid)
{
//var googlereCaptcha = _googlereCaptchaService.ResponceVerify(userDto.ReCaptchaToken);
//if (!googlereCaptcha.Result.success && googlereCaptcha.Result.score <= 0.5)
//{
// TempData["LoginSuccessMsg"] = "You are not Human.";
// return await Task.Run(() => View("SignIn"));
//}
var signedUser = await userManager.FindByEmailAsync(userDto.Email);
var result = await signInManager.PasswordSignInAsync(signedUser.Email, userDto.Password, userDto.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))
{
return LocalRedirect(returnUrl);
}
else
{
var roles = await userManager.GetRolesAsync(signedUser);
if (roles.Contains("Super Admin"))
return RedirectToAction("Dashboard", "User");
if (roles.Contains("Data Entry Operator"))
return RedirectToAction("BusinessList", "Business");
if (roles.Contains("Business Admin"))
return RedirectToAction("MyBusiness", "Business");
}
}
ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
}
return await Task.Run(() => View(userDto));
}
If you Want to user default login of asp.net Identity then you should get role like this after success result and then redirect will fix this issue
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
var userId = SignInManager.AuthenticationManager.AuthenticationResponseGrant.Identity.GetUserId();
if (UserManager.IsInRole(userId, "Super Admin"))
{
return RedirectToAction("Index", "DashBoard");
}}

Asp.net Identity : User.Identity.GetUserId() is always null and User.Identity.IsAuthenticated is alway false

See my code below:
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
string UserId = User.Identity.GetUserId();
return RedirectToAction("ClientDetails","Home");
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", "Account", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
The UserId is always null and User.Identity.IsAuthenticated is always false. But I can view the View ClientDetails which requires authentication.
I assume your example is the code from your AccountController.Login() method. I had the same problem as you but discovered that the User object won't be populated until the next request. Try this approach:
case SignInStatus.Success:
return RedirectToAction("DoWork", "Account");
public async Task<ActionResult> DoWork()
{
//this works
string UserId = User.Identity.GetUserId();
//return to View or Redirect again
}
For the "The UserId is always null" part of the question, you can look up the user by the model.UserName:
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
ApplicationUser user = UserManager.FindByName(model.UserName);
string UserId = user.Id;
// UserId is now populated
return RedirectToAction("ClientDetails","Home");
etc. Not sure if you wanted User.Identity.IsAuthenticated true or whether that was an observation - this doesn't change that part.
Worked with me after tagging the method with [Authorize]
attribute and sending the access-token in the authorize header, it seems that the access-token is needed to recognize the user

Resources