I am trying to enable the email confirmation on my MVC 5 app.
Here is my code as I keep get this error:
[InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]
This is my register action:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
First = model.First,
Last = model.Last
};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
UserManager.AddToRole(user.Id, "Default");
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 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
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return View("DisplayEmail");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and here is the email section
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
var emailManager = new EmailConfig(EmailConfig.EmailSystem, message.Destination, message.Subject, message.Body);
return emailManager.Send();
// Plug in your email service here to send an email.
}
}
and here is where the email actually gets sent
public Task Send()
{
foreach(var mail in this.mails)
{
this.client.SendMailAsync(mail);
}
return Task.FromResult(0);
}
Related
I'm working on this Dotnet core MVC/Razor Pages app and it is supposed to prevent users from accessing certain controller routes through the search bar unless they specified it in the register form. How do I achieve this? Does dotnet core MVC have a keyword for this? I'm stuck. Some relevant code for Identity/Account/Register that shows registering is below
[Required]
[Display(Name = "Choose businesslisting or choice")]
public string Decision { get; set; }
}
public void OnGet(string returnUrl = null)
{
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (Input.Decision == "Business Listing" || Input.Decision == "business listing")
{
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Change your password",
$"Please change your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
I would start with the ASP.NET Core Authorization documentation https://learn.microsoft.com/en-us/aspnet/core/security/authorization/introduction?view=aspnetcore-2.2
You might find claims based authentication useful in this scenario https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-2.2
Upon creating users you can add specific claims that allow them access to specific controllers/urls.
Today AspNet MVC causes the newly registered user to be logged in.
I need Asp.Net MVC to make the user after registering not logged in.
What class do I get the answer so that the user is not logged in after registering?
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
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);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// Code below is responsible for entering access level to the new registered user
if (Session["Anunciante"] != null)
{
ApplicationDbContext db = new ApplicationDbContext();
var role = db.Roles.Where(r => r.Name == "Anunciante").SingleOrDefault();
if (!UserManager.IsInRole(user.Id, role.Name))
{
await UserManager.AddToRoleAsync(user.Id, role.Name);
await UserManager.UpdateSecurityStampAsync(user.Id);
await UserManager.RemovePasswordAsync(user.Id);
}
return RedirectToAction("Create", "Empresas");
}
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// Se chegamos até aqui e houver alguma falha, exiba novamente o formulário
return View(model);
}
Just remove following code from the Register(RegisterViewModel model) method.
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
In my ASP.NET MVC Core 1.1.1 app with Individual User Accounts mode in VS2017 ver 15.3.3, I'm implementing Forgot Password functionality. The app is correctly sending the email with a generated link. I can open my email and can see the link generated as follows. Link correctly displays the ResetPassWord.cshtml view. But when I enter required user name, password, confirm password info and click on Submit button, I get the Invalid Token error. Question: How to resolve the issue?
NOTE:
A. The PasswordReset functionality works fine if I am logged in and want to reset my password to something else.
B. Not sure if it's relevant or not: I'm not using Email as a user name.
Instead I've changed the Email attributes in the AccountCountroller from
[Required]
[EmailAddress]
public string Email { get; set; }
to
[Required]
public string UserName { get; set; }
etc., etc. and have extended user attributes in ApplicationUser.cs as follows:
ApplicationUser.cs
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string UserFullName { get; set; }
[Column(TypeName = "varchar(50)")]
public string UserEmaill { get; set; }
[Column(TypeName = "varchar(15)")]
public string UserCity{ get; set; }
}
Body of my Email that App sent:
Please reset your password by clicking here: <a href='http://localhost/HPF/Account/ResetPassword?userId=1db73091-8098-4427-9a1a-1897dcabea90&code=CfDJ8IgLyyEqB6dPkvwFmW%2BJlPrg%2Fpz%2FiNTD442K1KoBG7MSx3zp6TFSY5w0Xj49cG87P%2BkGSWalD6YbbRlrLP4qAEORuoaHZM%2BMe0G08Y9EeUycbhNxx%2FpEv2z3sJ%2BMr4ZccLBO08iwlbIxBnXkJ5gu%2Bsnoo5FHAKysp85%2FrLdbS47smj%2Bt9kaaFA5DDBkzmR%2Bb0HTV2FL8CyjH2M1wCjLgcSC2Jg6BIcDAVUd7jEwxB8V9upAVh%2FR3FJCk2OAJU4w4Fg%3D%3D'>link</a>
Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByNameAsync(model.UserName);
string sUserEmail = user.UserEmail; //I Added this. Note the email is not the user name in my app
if (user == null)
{
// 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=532713
// Send an email with this link
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(sUserEmail, "Reset Password", $"Please reset your password by clicking here: <a href='{callbackUrl}'>link</a>");
return View("ForgotPasswordConfirmation");
}
// If we got this far, something failed, redisplay form
return View(model);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.FindByNameAsync(model.UserName);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
}
var result = await _userManager.ResetPasswordAsync(user, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction(nameof(AccountController.ResetPasswordConfirmation), "Account");
}
AddErrors(result);
return View();
}
UPDATE
I tried code = System.Net.WebUtility.UrlEncode(code); after the line var code = await _userManager.GeneratePasswordResetTokenAsync(user); inside ForgotPassword(...) POST method; but still the same error. I then also tried var code = System.Net.WebUtility.UrlDecode(model.Code); before the line var result = await _userManager.ResetPasswordAsync(user, code, model.Password); inside ResetPassword(...) POST method; same error again.
I have created user and assign the role. Both tables AspNetUsers and AspNetUserRoles gets updated. Ideally here, user is authenticated and authrorized. But when I try to navigate, it throws UnAuthorized access error. If I logout and try to login again, it works perfect. This problem is only at the time of creation of user. I have added [CustomAuthorize(Roles = "User")] to the controller I am accessing.
Sample Code is :
var user = new ApplicationUser { UserName = model.EmailID, Email = model.EmailID, PhoneNumber = model.PhoneNumber, FirstName = model.FirstName, LastName = model.LastName,Organisation = model.Organisation, isAdmin = model.isAdmin };
var result1 = await UserManager.CreateAsync(user, model.Password);
if (result1.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
await UserManager.AddToRoleAsync(user.Id, "User");
// 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
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
When I redirect to Home controller, the control comes into HandleUnauthorizedRequest function.
My custom authorize class is as follow:
public class CustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "AccessDenied" }));
}
}
}
The controller code is as follows:
[Authorize]
public class HomeController : Controller
{
MusicJournalContext db = new MusicJournalContext();
private static RoleManager<IdentityRole> RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
public ActionResult Index()
{
if (User.IsInRole("Admin"))
{
return RedirectToAction("Index", "Articles", new { status = "InProcess" });
} else if(User.IsInRole("User")){
return RedirectToAction("Index", "EndUser");
}
return View();
}
I found 1 solution, but it was not working. I have added await UserManager.UpdateSecurityStampAsync(user.Id); Help appreciated.
Use this instead of you method. You were skipping role check, because of that your user was getting unauthorized.
public class CustomAuthorize : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
var rolesArray = Roles.Split(',');
var isAllowed = rolesArray.Any(role => filterContext.HttpContext.User.IsInRole(role));
if (!isAllowed)
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "AccessDenied" }));
}
}
}
From my understanding is that when a user creates a new account in ASP.NET MVC, the method to handle this would be the Register method located in the AccountController class of any MVC Solution. This is how the current method of my class looks like:
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
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);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 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
// string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
// var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
// await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
However from what I noticed is that there is no code concerning dealing with Roles. My goal is to set a role for the first user to be created and make it an Admin role. All users that would register after that should differ from that role. How could I approach this in this code?
Edit: Would I have to use my Seed method of Migrations\Configuration.cs in order to achieve this instead? If so, how should my Seed method need to look like?
Yes you can use your seed Method for initialized your roles and first admin user.
For Example:
protected override void Seed(ApplicationDbContext context)
{
//initialized Role
context.Roles.AddOrUpdate(r => r.Name,
new IdentityRole { Name = "SuperAdmin" },
new IdentityRole { Name = "Admin" },
new IdentityRole { Name = "User" }
);
//initialized Admin user
if (!context.Users.Any(u => u.UserName == "ashiquzzaman#outlook.com"))
{
var store = new UserStore<ApplicationUser>(context);
var manager = new UserManager<ApplicationUser>(store);
var user = new ApplicationUser
{
UserName = "ashiquzzaman#outlook.com",
Email = "ashiquzzaman#outlook.com",
PhoneNumber = "+8801717252600",
//.......
};
manager.Create(user, "aSHIQ!109");
manager.AddToRole(user.Id, "Admin");
}
}