I have been given the requirement to provide the ability to create users through the UI with no password. I am trying to accomplish this using ASP.NET Identity.
I am able to successfully create a user without a password using the UserManager's Create method:
if (vm.ShouldHavePassword)
{
userManager.Create(userToInsert, vm.Password);
}
else
{
userManager.Create(userToInsert);
}
After the call to the Create method, the test user gets successfully saved into our AspNetUsers table. And when I do not provide a password, the PasswordHash column in our AspNetUsers table is set to NULL.
My issue is, I cannot login as the test user that does not have a password. The following is the method call that we use to validate a user's credentials:
result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
I attempted to login as a test user that has a NULL PasswordHash multiple times. To do this, I do not provide a password in our login form. As a result, a NULL password is passed into the PasswordSignInAsync method. The return value of this method call is always SignInStatus.Failure.
Using ASP.NET Identity, how can I configure my code to correctly authenticate user credentials when the credentials contain a NULL password, and the user in the database contains a NULL PasswordHash? Is such a thing even possible?
Yes you can. ASP.NET Identity Framework is fully customizable. Just override PasswordValidator.ValidateAsync and PasswordHasher.VerifyHashedPassword methods like this:
internal class CustomPasswordValidator: PasswordValidator
{
public override async Task<IdentityResult> ValidateAsync(string item)
{
if (string.IsNullOrEmpty(item)) return IdentityResult.Success;
return await base.ValidateAsync(item);
}
}
internal class CustomPasswordHasher : PasswordHasher
{
public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
if (hashedPassword == null && string.IsNullOrEmpty(providedPassword))
return PasswordVerificationResult.Success;
return base.VerifyHashedPassword(hashedPassword, providedPassword);
}
}
And set them like this:
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
manager.PasswordValidator = new CustomPasswordValidator();
manager.PasswordHasher = new CustomPasswordHasher();
Okay, what you need to do is find the user (AspNetUsers user) using your db context. After you have the user, you can check if their PasswordHash is null.
If yes, then just sign them in using SignInManager.SignIn.
If not, use SignInManager.PasswordSignIn.
example..
//alternatively, you can find the user using Email, Id or some other unique field
var user = db.AspNetUsers.FirstOrDefault(p => p.UserName);
if (user != null)
{
if (user.PasswordHash == null)
await SignInManager.SignInAsync(user, true, true);
else
await SignInManager.PasswordSignInAsync(model.UserName, model.Password,
model.RememberMe, shouldLockout: false);
}
Hope it helps.
I don't think you can validate user without password. As a workaround: Instead of blank password, I'll recommend to use some Dummy/Common password from C# code, both while creating User and while validating credential
When creating user
if (vm.ShouldHavePassword)
{
userManager.Create(userToInsert, vm.Password);
}
else
{
userManager.Create(userToInsert, "someDummy123$");
}
When validating
result = await SignInManager.PasswordSignInAsync(model.UserName, "someDummy123$", model.RememberMe, shouldLockout: false);
Related
I was building dot net core web app but identity system does not allow me to login.I figured that if my username and email address in database would not be the same it wont logged in.Anyone knows what is going on??
I'm not sure if I understood your question correctly, but the following login method on an account controller allows logins with either the username or the password:
[AllowAnonymous]
[HttpPost("login")]
public async Task<IActionResult> LoginAsync([FromBody]LoginPost model)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
// The user is identified either by Email or by Username
var user = await _userManager.FindByEmailAsync(model.Identifier)
?? await _userManager.FindByNameAsync(model.Identifier);
if (user == null)
{
return Unauthorized();
}
var signInResult = await _signInManager.PasswordSignInAsync(user, model.Password, true, false);
if (signInResult.Succeeded)
{
return NoContent();
}
return Unauthorized();
}
Please note the line where the user is looked up in the backing store: FindByEmail() ?? FindByUsername. This allows you to login with either username/password or email/password.
According to MSDN
Both the local log in and social log in check to see if 2FA is enabled. If 2FA is enabled, the SignInManager logon method returns SignInStatus.RequiresVerification, and the user will be redirected to the SendCode action method, where they will have to enter the code to complete the log in sequence. If the user has RememberMe is set on the users local cookie, the SignInManager will return SignInStatus.Success and they will not have to go through 2FA.
I do want the user to be able to use the remember me feature of the application but I can not figure out how to get the cookie to ditch this setting so that the SignInStatus returns RequiresVerifacation. I'm actually not even 100% sure that the cookie is causing it. All I know is that I have enabled TFA and in AspUsers table I can see that TwoFactorEnabled is set to true but the status is always returning as Success.
Here is the controller where I am not getting what I want
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
return RedirectToAction("Login");
}
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false });
case SignInStatus.Failure:
default:
// If the user does not have an account, then prompt the user to create an account
ViewBag.ReturnUrl = returnUrl;
ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
}
}
According to this MSDN page var result should return SignInStatus.RequiresVerification but it returns Success when returning from the OAuth sign in from google or just a regular sign in. The User has their TwoFactorEnabled set to true in the AspUsers Table which is what result is checking according to docs.
The solution to this problem was actually incredibly simple. You just need to KNOW WHAT YOU'RE DOING. ASP Identity is complex and there are a lot of moving parts. If anyone is coming across this post struggling with ASP.NET Identity I would recommend starting here with the Microsoft video series on customizing ASP Identity. There is a lot of info there. The tutorial most helpful for implementing a Google Authenticator style of TFA with QR codes is here
I'm using a Asp.NET MVC 5 project that came with a Bootstrap 3 theme we bought and in its login method they just look for the user based on his e-mail, the password is not validated. Login method below:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(AccountLoginModel viewModel)
{
// Ensure we have a valid viewModel to work with
if (!ModelState.IsValid)
return View(viewModel);
// Verify if a user exists with the provided identity information
var user = await _manager.FindByEmailAsync(viewModel.Email);
var hashPass = new PasswordHasher().HashPassword(viewModel.Password); // this is a line I added which gerenates a different hash everytime
// If a user was found
if (user != null)
{
// Then create an identity for it and sign it in
await SignInAsync(user, viewModel.RememberMe);
// If the user came from a specific page, redirect back to it
return RedirectToLocal(viewModel.ReturnUrl);
}
// No existing user was found that matched the given criteria
ModelState.AddModelError("", "Invalid username or password.");
// If we got this far, something failed, redisplay form
return View(viewModel);
}
The line I'm trying to insert the password validation is the if (user != null). I tried using _manager.Find(email,password) but it doesn't work.
How can I login the user with his e-mail and validate the password?
That is because you are hashing the password before trying to find the user.
Do
var user = _manager.Find(viewModel.Email, viewModel.Password);
// If a user was found
if (user != null)
{
//...other code removed for brevity.
which is the standard way to do it.
-------Try this code------
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return View("SuccessView");
case SignInStatus.Failure:
return View("LoginView");
}
I was wondering if there is a standard way of protecting a ASP.Net web application with just a single password? In other words no username needed and all clients use the same password for authentication.
Or does anyone have their own solution?
You simply could use Identity framework to aim this propose. Actually you don't need any user or password to authenticate.
[HttpPost]
public ActionResult Login(string password)
{
if (password=="MyVerySecretPassword")
{
var ident = new ClaimsIdentity(
new[] {
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, "JustAnuniqueName"),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
new Claim(ClaimTypes.Name,"JustAnuniqueName"),
},
DefaultAuthenticationTypes.ApplicationCookie);
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
return RedirectToAction("MyAction"); // auth succeed
}
// invalid password
ModelState.AddModelError("", "invalid username or password");
return View();
}
But it would be much better if you hash the password and check the hashed password instead of above simple if statement. To aim this you could use PasswordHasher class to hash and verify the password.
First hash your desired password and save it in preferred storage (DB, file, hard coded in code or everywhere else):
string hashedPassword = new PasswordHasher().HashPassword("MyVerySecretPassword");
Now since you have the hashed one. You could use VerifyHashedPassword() method to verify it.
if(new PasswordHasher()
.VerifyHashedPassword("myHashedPassword",password)==PasswordVerificationResult.Success)
{
// the password is correct do whatever you want
}
Also you could see my simple working example which I made to demonstrate it.
In MVC 4 with SimpleMembership all these functions come with the default webbapp that you create in Visual Studio.
I was wondering where I can find the same for MVC 5 using the new ASP.NET Identity membership system? Is there some official blog or something that is beeing hidden from me in google search results?
UPDATE1: http://blogs.msdn.com/b/webdev/archive/2013/12/20/announcing-preview-of-microsoft-aspnet-identity-2-0-0-alpha1.aspx
UPDATE2: ASP.NET Identity 2.0 RTM has been released. Forgot Password is included in the samples/templates. http://blogs.msdn.com/b/webdev/archive/2014/03/20/test-announcing-rtm-of-asp-net-identity-2-0-0.aspx
We are working on adding these features to the ASP.NET Identity system and the MVC 5 templates.
I ran into this as well. To fix it, I created some controller actions in AccountController.cs (and corresponding views) to handle it.
Here are the actual lines that reset the user's password:
[AllowAnonymous]
[HttpPost]
public ActionResult ResetForgottenPassword(string key, ManageUserViewModel model)
{
var user = db.Users.SingleOrDefault(u => u.ForgotPasswordCode != null && u.ForgotPasswordCode == key);
if (user == null || !user.ForgotPasswordDate.HasValue || user.ForgotPasswordDate.Value.AddDays(1) < DateTime.UtcNow)
return new HttpUnauthorizedResult();
ModelState state = ModelState["OldPassword"];
if (state != null)
{
state.Errors.Clear();
}
if (ModelState.IsValid)
{
if (UserManager.HasPassword(user.Id))
UserManager.RemovePassword(user.Id);
IdentityResult result = UserManager.AddPassword(user.Id, model.NewPassword);
if (result.Succeeded)
{
//Clear forgot password temp key
user.ForgotPasswordCode = null;
user.ForgotPasswordDate = null;
db.SaveChanges();
//Sign them in
var identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = false }, identity);
return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
}
else
{
AddErrors(result);
}
}
ViewBag.ForgotPasswordCode = key;
return View(model);
}
Some custom items are the new fields on the user object:
ForgotPasswordCode and ForgotPasswordDate to keep track of the user throughout the "reset password email" process.
I pass the key around in in the ViewBag once the user arrives from the email link.
The db variable is a property of my database context class inherited from a base controller.
I use UTC DateTimes in my database. Change DateTime.UtcNow to DateTime.Now if you do not.
Probably not the best solution, but it's a fairly quick and simple patch.
You can build a reset password by yourself (not sure that is the better choice, but is better than nothing)
Generate the hash with:
var newPwdHash = new PasswordHasher().HashPassword(newPasswordPlain)
And replace to the user's passwordhash property
If you cannot wait for the ASP.NET Identity Team to add this feature you can get an implementation of password reset from the open source project SimpleSecurity. Just take a look at the ResetPassword action on the AccountController. You can read about how the password reset was implemented here. Although the article references SimpleMembership, SimpleSecurity uses the same API to support either SimpleMembership or ASP.NET Identity in your MVC application.