I have a fairly standard implementation of Identity in my Core 2 application.
I have enabled the requirement for a confirmation email. This all works well - it generates and sends the email, with a valid link, which when clicks confirms the user's account. No problems there.
The situation is though, that if an email goes missing/is deleted/never arrives for whatever reason.
What I am doing, is using the standard login method, and if IsNotAllowed then I push them towards a page which tells them they need to activate their account, check their email etc.
On that page, I have a Form with a button which Posts to a controller to regenerate and resend the email.
I literally copy and pasted the code from the standard Register method into my custom method.
The problem is when clicking the link the Activation fails. I get an InvalidToken error.
Any ideas why and how to fix?
Login Method
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(model.Email,
model.Password, model.RememberMe, lockoutOnFailure : false);
if (result.IsNotAllowed)
{
var user = await _userManager.FindByEmailAsync(model.Email);
EmailConfirmationViewModel viewModel = new EmailConfirmationViewModel();
viewModel.Email = model.Email;
return RedirectToAction("AwaitingEmailConfirmation", viewModel);
}
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return RedirectToAction("Index", "Races");
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Register Method
[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,
FirstName = model.FirstName,
LastName = model.LastName,
MobileNumber = model.MobileNumber,
Marketing = model.Marketing,
Newsletter = model.Newsletter,
Source = "Website"
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
//await _createContact.CreateContactAsync(model.Email, model.FirstName,
//model.LastName, model.Marketing, model.Newsletter);
var fields = new Dictionary<string, string>();
fields.Add("firstname", model.FirstName);
fields.Add("lastname", model.LastName);
fields.Add("newsletter", model.Newsletter.ToString());
fields.Add("marketing", model.Marketing.ToString());
fields.Add("source", "Website");
string publicaccountid = "55ebcc8b-b23f-4843-9dcb-1df08811de65";
var createcontact = ElasticEmailClient.Api.Contact.AddAsync(publicAccountID: publicaccountid,
email : model.Email, field : fields, sendActivation : false);
//await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
EmailConfirmationViewModel viewModel = new EmailConfirmationViewModel();
viewModel.Email = user.Email;
return RedirectToAction("AwaitingEmailConfirmation", viewModel);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Resend Email Method
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> ResendConfirmationEmail(EmailConfirmationViewModel model)
{
var user = await _userManager.FindByEmailAsync(model.Email);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
return RedirectToAction("AwaitingEmailConfirmation", model);
}
Related
I am using Asp.net core for develop web application with google Authentication Login
I succeed to manage up to get google credential and register the User at very first. But the problems is even user register to the Application with Google Credentials,it always ask for register again and again for the application.
I found that it happen due to ExternalLoginSignInAsync function calling and it always give false for that ,I changed these parameters and tried several times
isPersistent:false/true) and bypassTwoFactor : true/false I have test above all variations. But it always give false for result .As well as I tried with Google Authentication with Normally Registered User login.it also gave me same result
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl =null,string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider:
{remoteError}";
return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var result = await
_signInManager.ExternalLoginSignInAsync(info.LoginProvider,
info.ProviderKey, isPersistent:false, bypassTwoFactor : true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider}
provider.", info.Principal.Identity.Name,
info.LoginProvider);
// return LocalRedirect(returnUrl);
return LocalRedirect("/Customer/ProjectsApiKey/");
}
Could you please any anyone who has already resolved this this problems help me to.I am expecting what should I do for check user already registered or not with Google Authentication
When GetExternalLoginInfoAsync returns false you can check user exists, if the user exists then add login.
Otherwise, the user does not have an account, then asks the user to create an account.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
if (result.IsLockedOut)
{
return RedirectToAction(nameof(Lockout));
}
else
{
var user = await _userManager.FindByEmailAsync(email);
if (user != null)
{
var resultTemp = await _userManager.AddLoginAsync(user, info);
if (resultTemp.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: true);
return RedirectToAction("Index", "Home");
}
}
// User does not have an account, then ask the user to create an account.
ViewData["ReturnUrl"] = returnUrl;
ViewData["LoginProvider"] = info.LoginProvider;
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
return View("ExternalLogin", new ExternalLoginViewModel { Email = email });
}
I found the fault finally , it happened due to Email Confirmation thing,In my startup.cs class I have added the
config.SignIn.RequireConfirmedEmail = true;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(config =>
{
config.SignIn.RequireConfirmedEmail = true;
})
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
// requires
// using Microsoft.AspNetCore.Identity.UI.Services;
// using WebPWrecover.Services;
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
due to this it always required to confirm the user Login email even we use Google Authentication.In order to fix this what I have did was when I create new user using Google Authentication login I set user parameters including EmailConfirmation=true addition to User name & User Email.Then that bug was fixed. after that when I call below function
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
// var user = new IdentityUser { UserName = Input.Email, Email = Input.Email }; comment dulith
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email ,EmailConfirmed=true};
var result = await _userManager.CreateAsync(user);
await _userManager.AddToRoleAsync(user, SD.User);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
// return LocalRedirect(returnUrl);
return LocalRedirect("/Customer/ProjectsApiKey/");
}
}
If user Exists
result.Succeeded is true
And also Thank you very much for previous answer.
After a little research and with the help of another fellow from here, i managed to create an "Admin" role and assign it to an user ( I only needed one Admin ). Now i have some troubles with making the login redirect to different pages regarding the role the user has.
I am using asp.net core 2.2 default web site template and authentication selected as individual user account.
Here is my CreateRole method:
private async Task CreateUserRoles(IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult roleResult;
//Adding Admin Role
var roleCheck = await roleManager.RoleExistsAsync("Admin");
if (!roleCheck)
{
//create the roles and seed them to the database
roleResult = await roleManager.CreateAsync(new IdentityRole("Admin"));
}
//Assign Admin role to the main User here we have given our newly registered
//login id for Admin management
ApplicationUser user = await userManager.FindByEmailAsync("example#gmail.com");
await userManager.AddToRoleAsync(user, "Admin");
}
And here is the post of my Login method with what i've tried so far:
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
var status = await _signInManager.UserManager.GetUsersInRoleAsync("Admin");
if (Input.Email.Contains(status.ToString()))
{
return LocalRedirect(returnUrl);
}
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
You need to fetch the user's role from the database:
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
var user = await _signInManager.UserManager.FindByEmailAsync(Input.Email);
// Get the roles for the user
var roles = await _signInManager.UserManager.GetRolesAsync(user);
if (roles.Any(role=>"Admin".Equals(role)))
{
return LocalRedirect("YourURL");
}
Or use UserManager.IsInRoleAsync function :
var user = await _signInManager.UserManager.FindByEmailAsync(Input.Email);
var isInRole = await _signInManager.UserManager.IsInRoleAsync(user, "Admin");
if (isInRole)
{
return LocalRedirect("YourURL");
}
Right after the user logged in you can check their role using HttpContext.User.IsInRole function:
var user = await _userManager.FindByEmailAsync(Input.Email);
var result = await _signInManager.PasswordSignInAsync(user, Input.Password, Input.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var principal = await _signInManager.ClaimsFactory.CreateAsync(user);
if (principal.IsInRole("Admin")) {
return LocalRedirect("SomeSpecialUrlForAdmin");
}
else {
return LocalRedirect(returnUrl);
}
}
I am using SignInManager and AuthenticationManager through dependency injection. All working fine except SendTwoFactorCodeAsync
Here is the code:
UtilityConfig
container.RegisterType<ApplicationSignInManager>();
container.RegisterType<ApplicationUserManager>();
Account Controller
var myUser = await UserManagerService.Value.FindAsync(model.Mobile, model.Password);
if (myUser != null)
{
var result = await SignInManager.Value.PasswordSignInAsync(model.Mobile, model.Password, false, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
{
//success code
}
case SignInStatus.RequiresVerification:
{
var code = await UserManagerService.Value.GenerateTwoFactorTokenAsync(myUser.Id, ConfigurationManager.AppSettings["PhoneCodeProvider"]);
var send = await SignInManager.Value.SendTwoFactorCodeAsync(ConfigurationManager.AppSettings["PhoneCodeProvider"]); //here throughing exception
if (send)
{
SmsHelper.Value.SendSmsBySmsBatch(user.UserName, Constant.Instance().ProductName, myUser.UserName, "Code is " + code, true);
return RedirectToAction("TwoFactorCode", new { Provider = ConfigurationManager.AppSettings["PhoneCodeProvider"], ReturnUrl = returnUrl, RememberMe = false });
}
}
break;
---------------
}
Also tried this:
var userId = await SignInManager.Value.GetVerifiedUserIdAsync(); // this is returning 0
var user = await UserManagerService.Value.FindByIdAsync(userId);
if (user != null)
{
var code = await UserManagerService.Value.GenerateTwoFactorTokenAsync(myUser.Id, ConfigurationManager.AppSettings["PhoneCodeProvider"]);
var send = await SignInManager.Value.SendTwoFactorCodeAsync(ConfigurationManager.AppSettings["PhoneCodeProvider"]); //here throughing exception
if (send)
{
SmsHelper.Value.SendSmsBySmsBatch(user.UserName, Constant.Instance().ProductName, user.UserName, "Your security code is " + code, true);
return RedirectToAction("TwoFactorCode", new { Provider = ConfigurationManager.AppSettings["PhoneCodeProvider"], ReturnUrl = returnUrl, RememberMe = false });
}
}
strange thing is that on first try I get this issue and on second try it works fine (return all results perfectly) .. again I logout and try .. first time get issue second try it works .
Not able to track the reason of this failure, what can be the best way to solve this
I get the exact same error. I tried adding case SignInStatus.RequiresVerification: Thread.Sleep(500)
to see if it was just a delay but to no avail.
It requires a redirect as the SendTwoFactorCodeAsync checks to see if Request.IsAuthernticated. Which the request is not, but there is a valid log in.
It is a bug and should be reported on github
here is my code:
[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.Trim(), model.Password.Trim(), model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
var userId = await SignInManager.GetVerifiedUserIdAsync();
if (userId == null)
{
return RedirectToAction("redirectSendTwoFactorCodeAsync", "Account",
new
{
ReturnUrl = returnUrl,
Email = model.Email,
Password = model.Password,
RememberMe = model.RememberMe
});
}
if (!await SignInManager.SendTwoFactorCodeAsync(twoPartCode))
{
ModelState.AddModelError("", "Failure sending Email verification token");
return View(model);
}
return View("VerifyCode", new VerifyCodeViewModel { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
[HttpGet]
[AllowAnonymous]
public async Task<ActionResult> redirectSendTwoFactorCodeAsync(string ReturnUrl, string Email, string Password, bool RememberMe)
{
if (!await SignInManager.SendTwoFactorCodeAsync(twoPartCode))
{
ModelState.AddModelError("", "Failure sending Email verification token");
return View("Login", new LoginViewModel()
{
Email = Email,
Password = Password,
RememberMe = RememberMe
});
}
return View("VerifyCode", new VerifyCodeViewModel { ReturnUrl = ReturnUrl, RememberMe = RememberMe });
}
I have the following I use to change the UserName
// POST: /Manage/ChangeUserName
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ChangeUserName(ChangeUserNameViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = UserManager.FindById(User.Identity.GetUserId());
user.UserName = model.NewUserName;
var result = await UserManager.UpdateAsync(user);
if (result.Succeeded)
{
return RedirectToAction("Index", new { Message = ManageMessageId.ChangeUserNameSuccess });
}
AddErrors(result);
return View(model);
}
It successfully changes the name in the DB.
However once I log out. I can't log back in.
The user is still in the DB.
And the Login code
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
Just returns SignInStatus.Failure
No other errors are occurring.
Any ideas why that is occurring?
I want to skip external login register - association form
I am using google external login with MVC5,
If you login with google account, it will show you above screen after entering google credentials first time. I just want to skip this screen.
Above view get return from
[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 user = await UserManager.FindAsync(loginInfo.Login);
if (user != null)
{
await SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
// 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 });
}
}
return View("ExternalLoginConfirmation", new
ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
Above code return ExternalLoginConfirmation view and shows above screen. on submit above form it will submitted to below action
public async Task
ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model,
string returnUrl)
To skip above register screen, I need to call ExternalLoginConfirmation from ExternalLoginCallback, so How can I do that.
Well, didn't take too long to put this together. I haven't tested it, but if it doesn't work as-is, should be really close. I mostly just copied the code from ExternalLoginConfirmation to ExternalLoginCallback:
// Sign in the user with this external login provider if the user already has a login
var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: 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 = false });
case SignInStatus.Failure:
default:
// Added this section to automatically create account if we have an email address
if (!string.IsNullOrEmpty(loginInfo.Email))
{
var user = new ApplicationUser { UserName = loginInfo.Email, Email = loginInfo.Email };
var createUserResult = await UserManager.CreateAsync(user);
if (createUserResult.Succeeded)
{
var addLoginResult = await UserManager.AddLoginAsync(user.Id, loginInfo.Login);
if(addLoginResult.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
return RedirectToLocal(returnUrl);
}
}
}
// 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 });
}