ASP.NET MVC Login by email instead of username - asp.net-mvc

I try to create new project with MVC 5 and OWIN by "Individial User Accounts". The problem is when I register a new account, the system requires me input an email then that email will be populated to both Email and Username in database.
When I try to login, the system asks me to input the email but it compares that email with column Username. So the values of Email and Username are the same. The login code below is in action Login (Account Controller)
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
Is there any ways to compare the inputted email with the Email column instead of Username?

To answer:
Is there any ways to compare the inputted email with the Email column instead of Username?
This is probably not the best answer but the SignInManager should have access to UserManager which has a Users property.
So you could lookup the user based on the email and then using the result call the signin method.
Something like this (untested):
var user = SignInManager.UserManage.Users.Where(u => u.email == model.Email).FirstOrDefault();
var result = await SignInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, shouldLockout: false);
You would want to check that the user was populated otherwise return invalid login details error. Same as if PasswordSignInAsync failed.

Here is what to do as #ChrisMoutray suggested, find the username for the email provided and log in. I put it here.
Instantiate usermanager and signinmanager
private readonly SignInManager<ApplicationUser> signInManager;
private readonly UserManager<ApplicationUser> userManager;
public AccountController(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
this.signInManager = signInManager;
this.userManager = userManager;
}
and then in the Login (post) task:
var user = await userManager.FindByEmailAsync(model.Email);
var result = await signInManager.PasswordSignInAsync(user.UserName, model.Password, model.RememberMe, lockoutOnFailure: false);
PS. Found myself in this old post so just adding a working solution.

The reason is because when you register. you get username = email.
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);

Related

How to retrieve loginProvider name ASP.Net MVC Core for ExternalLoginConfirmation method

In the ExternalLoginConfirmation method of the AccountController.cs a new user will be created according to user = new ApplicationUser { UserName = model.Email, Email = model.Email }.
I want to create the new user prepending the login provider name in the UserName property:
user = new ApplicationUser { UserName = provider ?? model.Email + model.Email, Email = model.Email };
My idea is to try to do like that:
var loginProviders = _signInManager.GetExternalAuthenticationSchemes().ToList();
var provider = loginProviders[index].DisplayName.ToString();
How can I select index to return the used loginProvider?
Unfortunately var provider = loginProviders.DisplayName.ToString(); does not work.
For Authentication I'm using
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
});
app.UseGoogleAuthentication(new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"]
});
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions()
{
ClientId = Configuration["Authentication:Microsoft:ClientId"],
ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"]
});
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"]
});
While testing my web site I use my credential.
Letting UserName = model.Email, Email = model.Email turns out to give an error.
I can use the same Email, by setting up
services.AddIdentity<ApplicationUser, IdentityRole>(opts => {
opts.User.RequireUniqueEmail = false;
})
But I can not have the same UserName
Any idea?
Thanks a lot.
In the meantime I've found the solution.
In the method
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
you can get the information about the user from the external login provider by calling
var info = await _signInManager.GetExternalLoginInfoAsync();
You can then add a new user by simply prepending info.LoginProvider in the UserName field like that
var user = new ApplicationUser { UserName = info.LoginProvider + model.Email, Email = model.Email };
Doing that you can use your credentials (you email address) to test multiple login provides avoiding conflics while trying to insert in the dbo.AspNetUsers identical UserName fields.
Hope it helps!

Invalid login attempt in MVC

I am currently changing the default template of MVC5 project (with identity). When I first time register and login, all goes well. But when I made some changes like project at default level stores UserName as Email. But when I make change it to store UserName as from Form then it gives 'Invalid' login attempt. Can anyone tell me that where I am going wrong. Either I have to make changes at any other places if yes then where?
Here are my register function:
Before:
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
I have changed this line to this:
var user = new ApplicationUser { UserName = model.usrName, Email = model.Email , PhoneNumber=model.Contact};
And my login function is as it comes in default. like,
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);
}
}
Now, can someone please tell me where i am wrong?
If you want to change UserName from model.Email to model.usrName, you have to use model.usrName in your Login function. Like this:
var result = await SignInManager.PasswordSignInAsync(model.usrName, model.Password, model.RememberMe, shouldLockout: false);
Because UserName and Password fields are default unique parameters to login. So it wants to complete login operation with UserName field.

Why user is null

I would like to ask, why UserId is null (in code below) after user log in.
OR
I need redirect users depending on the they roles. I need make it As Simple As Possible.
// POST: /Account/Login
[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.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
string UserId = User.Identity.GetUserId(); <------ HERE
HelpRoles hr = new HelpRoles();
returnUrl = hr.CheckUserRoleAndRedirect(UserId);
return RedirectToLocal(returnUrl);
The User object is set based on the decrypted cookies after the request is authenticated, and these cookies aren't set until the next request so the page needs to be refreshed before User.Identity is not null.
For more info, see this post.
To work around this, within the success case you can get the user from the DB using the email address and use the ID on that instead.
Like John Mc said to work around this you have to the get user in the Success case. So you might want something like this before getting UserId
var user = userManager.Find(model.UserName, model.Password);
or
var user = userManager.FindByEmail(model.Email);
or
var user = userManager.FindByName(model.Username);
EDIT
In this case you wouldn't have to use your method as shown below
string UserId = User.Identity.GetUserId(); <------ HERE
because once you get the user object you can get the id field in it like user.Id

Linking external logins to existing user

I'm quite new to Identity and trying to learn by watching videos in https://channel9.msdn.com/Series/Customizing-ASPNET-Authentication-with-Identity
In the default ASP.Net MVC template, you can link multiple external logins (google, facebook) to your account (through /Manage) if you are already logged in.
But what if the user first logged in to our website using their google account and log out from it and on another day tried to login using their facebook account. Assuming both of their facebook and google accounts uses the same email address, the user will not be able to login to the website because the default template doesn't allow that as UserManager.CreateAsync is going to fail. I know they can change their email and login, but that will create two different accounts for the same user.
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
I altered the code in ExternalLoginConfirmation so that it checks if the user exist and ads the new external provider to AspNetUserLogins. Can anyone please tell me if this is the right way to do this? or if there is a better way of doing it.
if (ModelState.IsValid)
{
// Get the information about the user from the external login provider
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
return View("ExternalLoginFailure");
}
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
//new code begins
else if (UserManager.FindByEmail(model.Email) != null)
{
var usr = await UserManager.FindByEmailAsync(model.Email);
result = await UserManager.AddLoginAsync(usr.Id, info.Login);
if (result.Succeeded)
{
await SignInManager.SignInAsync(usr, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
//new code ends
AddErrors(result);
}
I'm also fairly new to Identity and have come across the same problem, though my solution is quite different. I don't use Entity Framework and to achieve this I've had to basically rewrite the entire Identity engine with custom classes. I have my own Sql Server tables which are different to those created by EF. EF stores identity in 5 tables: users, roles, userroles, userclaims and userlogins. I only use the first three. In my environment AddLoginAsync is not required because the table doesn't exist. I store all local/external logins and registered users in the user table. Claims are stored as userroles when required.
The way I got around duplicate UserNames and Emails (logging in using different providers with the same registered email addresses) was to remove the validation check for existing usernames and emails before creating a user, using a custom UserValidator. The table allows for duplicates. When logging in I do a custom check for uniqueness based on username/provider (external) or email/passwordhash (local). It seems to be working.

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).

Resources