I am building an MVC5 application with ASP.NET Identity 2.0 and EF 6.1.1.
This is part of my current Login method:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
return View(model);
var result = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user == null)
break;
if (!UserManager.IsInRole(user.Id, "OfficeUser"))
break; // << I always hit this line - even if the user is an OfficeUser
This works fine until I hit UserManager.IsInRole(). This always returns false.
Somewhere I read that IsInRole() will fail if the corresponding user is not signed in. But since I pass SignInManager.PasswordSignInAsync() I believe this should be fine.
And yes, I have checked the database many times and very carefully ;) My test user and my test role "OfficeUser" are definitely assigned to each other.
Does anybody have an idea?
I had same kind of issue, then I got work it as below. The reason seems to be, the user not completely signin or completed all authentication process in this stage.
case SignInStatus.Success:
{
var user = await UserManager.FindAsync(model.UserName, model.Password);
var roles = await UserManager.GetRolesAsync(user.Id)
if (user == null)
break;
if (roles.Contains("OfficeUser"))
break; // << I always hit this line - even if the user is an OfficeUser
Related
I would like to implement the following flow for two factor authentication in asp.net mvc:
var res = sign.PasswordSignIn("myusername", "mypassword", false, false);
if(res == SignInStatus.RequiresVerification)
sign.SendTwoFactorCode("EmailCode");
However I'm finding that the SendTwoFactorCode function is returning false and not sending the email because internally it is checking if the user is verified. See this line in the source. If I make a second request the call to SendTwoFactorCode works as I'm expecting.
Is there a way to make SendTwoFactorCode work correctly immediately after a call to PasswordSignIn?
Is there a way to make SendTwoFactorCode work correctly immediately after a call to PasswordSignIn?
Short answer: No
Suggested alternative:
The flow usually suggested in documentation is
Authenticate user via username and password.
If valid user and password, and requires additional verification because 2FA is enabled then redirect to the page to Send the code.
If user initiates the sending of the code, the code is sent and the user is then redirected to the verification page.
The second step is usually to confirm a provider, if there are multiple (ie SMS, email,...etc), to use for 2nd factor verification.
For example, the following does the redirect on RequiresVerification result
Account/Login
//...
var result = await signInManager.PasswordSignInAsync(username, password, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
But since you have already determined that you are going to send the code via email then you can skip the second step and redirect directly to the verify code which can be where the code is sent and verified.
Account/VerifyCode
[AllowAnonymous]
public async Task<ActionResult> VerifyCode(string returnUrl) {
var provider = "EmailCode";
// Require that the user has already logged in via username/password
var userId = await signInManager.GetVerifiedUserIdAsync();
if (userId == null) {
return View("Error");
}
// Generate the token and send it
if(!await signInManager.SendTwoFactorCodeAsync(provider)) {
return View("Error");
}
var model = new VerifyCodeViewModel {
ReturnUrl = returnUrl
};
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model) {
var provider = "EmailCode";
if (!ModelState.IsValid) {
return View(model);
}
var result = await signInManager.TwoFactorSignInAsync(provider, model.Code, false, false);
switch (result) {
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("VerifyCode", new { ReturnUrl = returnUrl });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
This should allow the TwoFactorCookie to be included in the next request so that GetVerifiedUserIdAsync behaves as expected.
/// <summary>
/// Get the user id that has been verified already or null.
/// </summary>
/// <returns></returns>
public async Task<TKey> GetVerifiedUserIdAsync()
{
var result = await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.TwoFactorCookie).WithCurrentCulture();
if (result != null && result.Identity != null && !String.IsNullOrEmpty(result.Identity.GetUserId()))
{
return ConvertIdFromString(result.Identity.GetUserId());
}
return default(TKey);
}
Source
Just like when you indicated
If I make a second request the call to SendTwoFactorCode works as I'm expecting.
That second request is important as it will include the cookie set in the previous request.
Reference How SignInManager checks for 2FA requirement
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've been banging my head against a wall for some time now about this:
I have an ASP.NET MVC 5.2.3 web application with ASP.NET Identity 2.2.1. I want to force users to
validate their email-address and
validate their mobile phone number.
So when a user registers for the application an emailVerification token is generated and sent to the user.
After that the user is redirected to the VerifyPhoneNumber endpoint in the Manage controller. SMS-code is generated and gets send to the user. User is promted to enter the SMS-code. Code is verified.
BUT if then the user receives the email with the email-verification-code and click the link the token cannot no longer be verified (Invalid Token).
As far as I understand, this happens because calling UserManager.ChangePhoneNumberAsync changes the user's SecurityStamp. Email-verification works well if phone verification is not active. To be more specific, when ChangePhoneNumberAsync is not called.
Any ideas on how to prevent the SecurityStamp from changing or allow both verifications on inital registration are greatly appreciated.
Ben
VerifyPhoneNumber
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var userId = User.Identity.GetUserId();
var result = await UserManager.ChangePhoneNumberAsync(userId, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToAction("Index", "Home");
}
else
{
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Could not verify phone number.");
return View(model);
}
ConfirmEmail
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
code = HttpUtility.UrlDecode(code);
var result = await UserManager.ConfirmEmailAsync(userId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
In the code that you get in the ASP.NET MVC 5.2.3 templates with Visual Studio 2015 Community RC, if you run them as they came, and if you register with your email address (and not with an external service provider such as Facebook or Google or Linked In or Twitter), and then if you login into the website by entering your user name and password, it straight-away lets you login and does not trigger two-factor authentication. It just logs you in successfully.
Specifically, the PasswordSignInAsync method on the SignInManager always returns a SignInStatus of Success if you enter your correct user name and password. It never evaluates to SignInStatus.RequiresVerification.
[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: true);
switch (result)
{
case SignInStatus.Success:
// if I sign-in with my correct user name
// and password, the flow-of-control always
// comes here. The SignInStatus never evaluates
// to RequiresVerification
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
// the flow-of-control never reaches here
return RedirectToAction("SendCode",
new
{
ReturnUrl = returnUrl,
RememberMe = model.RememberMe
});
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
This happens even though the default code has got two-factor authentication enabled and set up as indicated by the following snippets of code.
In Startup.ConfigureAuth
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie,
TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(
DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
In ApplicationUserManager.Create, the factory method.
// Register two factor authentication providers. This application uses Phone
// and Emails as a step of receiving a code for verifying the user
// You can write your own provider and plug it in here.
manager.RegisterTwoFactorProvider("Phone Code",
new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code",
new EmailTokenProvider<ApplicationUser>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
var container = Unity.Container;
manager.EmailService = container.Resolve<EmailService>();
manager.SmsService = container.Resolve<SmsService>();
I've got my EmailService and SmsService set up in a Unity container and they're configured properly.
What else do I need to do to set it up correctly? I have read this article and a few pieces of documentation from the MSDN, and a few forums posts on other websites about setting this up, but I am not very certain if I am missing something.
This guy isn't called / redirected to from anywhere. I guess this is what's missing.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EnableTwoFactorAuthentication()
{
await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(),
true);
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
await SignInManager.SignInAsync(user, isPersistent: false,
rememberBrowser: false);
}
return RedirectToAction("Index", "Manage");
}
It does seem that I am missing the part where I have to specifically have the user call the EnableTwoFactorAuthentication action as there is currently no call to it, but I can't be sure how that should integrate with the rest of the login workflow.
Click f12 from your browser and delete application Cookies