I have a few ideas on how to the default Register Action from the default asp.net core AccountController. It would be a lot better, safer and more secure if it were seperate. But I'm also curious on various ways to handle this.
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser {
UserName = model.Email, // CHANGE THIS
Email = model.Email
};
var result = await _userManager.CreateAsync(user, model.Password);
Also, how can I query the username input in realtime, so I can make sure it's unique to each user.
Related
I'm able to authenticate using anything for a password. The email has to be a valid registered email, but the pwd doesn't matter. Everything else is working normally.
Any suggestions on where to start trouble shooting this? I haven't found any similar issues in web searches.
My view...
My action in the account controller...
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
var user = _manager.FindByEmail(viewModel.Email);
if (user != null)
{
await SignInAsync(user, viewModel.RememberMe);
string uid = user.Id;
return RedirectToLocal(viewModel.ReturnUrl);
}
ModelState.AddModelError("", "Invalid username or password.");
return View(viewModel);
}
and the signinasync method...
private async Task SignInAsync(IdentityUser user, bool isPersistent)
{
// Clear any lingering authencation data
FormsAuthentication.SignOut();
// Create a claims based identity for the current user
var identity = await _manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
// Write the authentication cookie
FormsAuthentication.SetAuthCookie(identity.Name, isPersistent);
}
I did create a seperate MVC web project to see the scaffolded login action, which is quite a bit different. The SmartAdmin template is customized enough that its difficult to start changing things without knowing what I'm effecting. Any direction is appreciated.
If username in your system is email, you should use
var user = _manager.FindAsync(viewModel.Email, viewModel.Password);
and then signin the user if it's not null.
If username is not email, you should first get the user and then check for password
var user = _manager.FindByEmail(viewModel.Email);
bool isPasswordCorrect = await _manager.CheckPasswordAsync(user, viewModel.Password);
I am creating an MVC application and want to seed the database with a single admin user, who will have the ability to create other users. I'm currently doing as follows:
ApplicationUser admin = new ApplicationUser
{
UserName = "whatever",
Email = "whatever#mailinator.com",
EmailConfirmed = true
};
string pwd = "password";
var chkUser = await _userManager.CreateAsync(admin, pwd);
if (chkUser.Succeeded)
{
await _userManager.AddToRoleAsync(admin, Constants.Roles.Administrator);
}
This creates a user in the database, exactly as would be expected. But when I try to login with that user, I fail.
Screenshot of the failure
This is especially puzzling to me, because I use almost the exact same code when users register themselves through the GUI, but there everything is working fine.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
a bunch of stuff regarding sending a confirmation email
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);'
In both methods, the variable _userManager is of type UserManager<ApplicationUser> and is supplied to my method through dependency injection.
When a user logs in, he is looked up by username. For the users that are created through the GUI, their email address and username are identical. But my "seed" user has a username that is different from his email address. So when he tries to login, the system tries to find a user whose username matches my seed user's email address--and fails.
We get the invalid token error messages when a user tries to reset his password on the reset password screen after entering the new password. Normally this works just fine for everyone even with some special character like #. We have now a case where someone puts in * in his new password on the reset pw screen, gets this error message just because of this special character.
I've tried hours of research now to find a solution to why this happens but with no luck. I've found this solution here which has an issue with special characters in the username but we don't have that issue. There is only an issue with that special character in the password. As we are already in production we can't just disallow that character in passwords.
Someone got a clue?
Generating the token controller method:
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.Email.ToLower());
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user.UserName)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
// 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
var code = await _userManager.GeneratePasswordResetTokenAsync(user.UserName);
code = HttpUtility.UrlEncode(code);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.UserName, code = code }, protocol: Request.Url.Scheme);
await _emailService.CreateResetPasswordEmailAsync(user, callbackUrl);
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Reset password controller method:
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.FindByNameAsync(model.Email.ToLower());
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await _userManager.ResetPasswordAsync(user.UserName, HttpUtility.UrlDecode(model.Code), model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
AddErrors(result);
return View();
}
The problem is that you are double encoding the reset token. Here:
var code = await _userManager.GeneratePasswordResetTokenAsync(user.UserName);
code = HttpUtility.UrlEncode(code); //<--problem is this line
var callbackUrl = Url.Action("ResetPassword", "Account",
new { userId = user.UserName, code = code }, protocol: Request.Url.Scheme);
you encode the token and then Url.Action will do it again. So the solution is to not encode manually and let MVC handle it for you - just remove the second line here.
Also, on the other end, there's now no need to decode again, so your code there will be:
var result = await _userManager.ResetPasswordAsync(user.UserName,
model.Code, model.Password);
I am using asp.net identity 2 & MVC 5 on azure websites. I created a few roles and have no problem assigning users (registered to the site as a local account) to the newly created roles .
The weird part comes only when i create an account using social log in (Facebook in this case).
I checked the tables [aspnetusers] & [aspnetuserlogins], everything looks ok...
Until i try assigning social accounts with roles. There was no exception and in my logger everything looks fine. What am i doing wrong?
var um = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
var user = um.FindByEmail(email);
um.AddToRole(user.Id, "admin");
Just to be a little naggy: the role exists and i have no trouble assigning it to local accounts
After searching through stackoverflow, i finally stumble upon this comment AddToRole() method doesn't result in db entry in ASP.NET Identity
after which, i've changed the line
um.AddToRole(user.Id, "admin");
to
var result = um.AddToRole(user.Id, "admin");
//log result.Errors
Error message from .AddToRole()
User name Lee Gary is invalid, can only contain letters or digits.
After looking at the accounts creating locally, then i realised that all those accounts had their email as userid
AccountController - Register
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser()
{
// this line assigned email as username
UserName = model.Email,
Email = model.Email
};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
however, for god knows why, the MVC developers decided to let social log in users to enter their own usernames with no validation
AccountController - ExternalLoginConfirmation
//
// POST: /Account/ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Manage");
}
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.Name, //<--- model.Name = user input
Email = model.Email ,
BirthDate = model.BirthDate
};
IdentityResult result = await UserManager.CreateAsync(user);
if (result.Succeeded)
{
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
}
AddErrors(result);
}
ViewBag.ReturnUrl = returnUrl;
return View(model);
}
Finally to make sure that the username is A-OK, i've made a small change in Register()
From:
var user = new ApplicationUser()
{
UserName = model.Name,
Email = model.Email ,
BirthDate = model.BirthDate
};
To:
var user = new ApplicationUser()
{
Name = model.Name,
UserName = model.Email,
Email = model.Email ,
BirthDate = model.BirthDate
};
I have a question, I'm new to identity, but still i would like to know what would be the correct way of assigning role to a user when he is registering?
I have here a code:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
IdentityRole role = new IdentityRole("Admin");
await RoleManager.CreateAsync(role);
// Store Gender as Claim
user.Claims.Add(new IdentityUserClaim() { ClaimType = ClaimTypes.Gender, ClaimValue = "Male" });
//user.Roles.Add(new IdentityUserRole() { RoleId=role.Id, UserId=user.Id });
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//await UserManager.AddToRoleAsync(user.Id, "Admin");
await SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is just a test code, but basically if i use method UserManager.AddToROleAsync( ...) it works, BUT, it only happens after the user is added, so basically i do twice the roundtrip to database.
I tried doing it with user.Roles.Add(...) but i get an error when running it.
So my question would be what is the most efficient and correct way of doing it?
I don't know if there's a better way. I normally to it the same way as you do, first creating the role (if it doesn't exist), then creating the user, and as a last step adding the user to the role.
To use user.Roles.Add(...) the role must be present. The reason is the database (in this case Entity Framework and SQL Server). When looking closer at the Identity database you'll see that there is a relationship between the AspNetRoles and AspNetUsers table through the AspNetUserRoles table which has the UserId and the RoleId as a key. That means you can't add a user to a role when the user does not exist yet (and vice versa). So in my opinion you have to do twice the roundtrip (if you don't directly work on the context).
This works fine (Asp.Net Core Identity):
var role = await this.roleManager.FindByNameAsync( "Admin" );
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var userRole = new IdentityUserRole<int> {
RoleId = role.Id,
};
user.Roles.Add(userRole );
var result = await this.userManager.CreateAsync( user, model.Password);