Control not hitting SendAsync function of Custom EmailService : MVC Email confirmation - asp.net-mvc

I have created a new MVC Project in Visual Studio 2013. I noticed that the IdentityConfig.cs file was missing. I have heard that Microsoft removed it from newer versions of ASP.NET Identity. This file, when present, used to define the EmailService class.
So I implement my own EmailService class. The code looks like this
//EmailService.cs
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
await configGMailAsync(message);
}
private static async Task configGMailAsync(IdentityMessage message)
{
//mailing code
}
}
In my AccountController, I have the following Register method, which makes a call to UserManager.SendEmailAsync() method.
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
var provider = new DpapiDataProtectionProvider("myAppName");
UserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"));
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 <a href=\""
+ callbackUrl + "\">here</a>");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
However, after the call to UserManager.SendEmailAsync(), the control (debugger) never hits the SendAsync() function of EmailService class.
I have another project where the IdentityConfig.cs was automatically added on project creation. There, after the call to UserManager.SendEmailAsync(), the control hits the SendAsync() function.
What am I missing here?

Turns out that you have to register your Service with the UserManager class before you send the mail. Upon adding the following line just above UserManager.SendEmailAsync(), the SendAsync() function is picked up successfully:
UserManager.EmailService = new EmailService();
Here is the full function including the newly added line
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInAsync(user, isPersistent: false);
var provider = new DpapiDataProtectionProvider("myAppName");
UserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("EmailConfirmation"));
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code },
protocol: Request.Url.Scheme);
UserManager.EmailService = new EmailService();
await UserManager.SendEmailAsync(user.Id,
"Confirm your account", "Please confirm your account by clicking <a href=\""
+ callbackUrl + "\">here</a>");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
}
// If we got this far, something failed, redisplay form
return View(model);
}

I created a new Web API 2 project with individual user accounts in VS 2017. There IdentityConfig.cs was present but it did not have an EmailService class. If this is the case and you do not wan't to write UserManager.EmailService = new EmailService(); every time you wan't to use this feature you can add it here instead.
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
manager.EmailService = new EmailService();

Related

How to implement remember me functionality properly? Asp.Net Core

I have been trying for 2 days to know how to build remember me functionality, but there is nothing clear.
First and foremost, I would like to make sure we agreed of the workflow of this properly as follows.
I need here to allow users to open their profile with no need to
signIn again for 1 month, as long as the user doesn't logOut.
I used cookie-based authentication to store some data that I can check every time when user profile opened to make sure that user is authenticated.
-- there is no problem with this step
I use in this step simple code to retrieve data again from the cookie.
-- and here is the problem comes. I can retrieve data from the cookie as long as I'm loggedIn, otherwise, when I stop and re-run the application and redirect to the user profile directly without logIn again I can't read the cookie data although it still exists!!!
Now let's take a look at code
Startup File Cookie Setting
public void ConfigureServices(IServiceCollection services){
.....
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "RememberMecookie"; // cookie name
options.LoginPath = "/Account/LogIn"; // view where the cookie will be issued for the first time
options.ExpireTimeSpan = TimeSpan.FromDays(30); // time for the cookei to last in the browser
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
options.EventsType = typeof(CookieAuthEvent);
});
.....
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
.....
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
}
.....
public class CookieAuthEvent : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
context.Request.HttpContext.Items.Add("ExpiresUTC", context.Properties.ExpiresUtc);
}
}
}
Login ViewModel
public class VMLogin
{
public string UserName { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
Controller/Login
[HttpPost]
public async Task<IActionResult> LoginAsync(VMLogin CurrentUserLog, string returnUrl)
{
if (!string.IsNullOrEmpty(CurrentUserLog.UserName) && string.IsNullOrEmpty(CurrentUserLog.Password))
{
return RedirectToAction("Login");
}
if (ModelState.IsValid)
{
var SignInStatus = await signInManager.PasswordSignInAsync
(CurrentUserLog.UserName, CurrentUserLog.Password, CurrentUserLog.RememberMe, false);
AppUser _user = await userManager.FindByNameAsync(CurrentUserLog.UserName);
if (SignInStatus.Succeeded)
{
if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) // to prevent login from outside link
{
return Redirect(returnUrl);
}
else
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, CurrentUserLog.UserName),
new Claim(ClaimTypes.Email, _user.Email),
new Claim(ClaimTypes.NameIdentifier, _user.Id.ToString())
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var props = new AuthenticationProperties{
IsPersistent = true,
ExpiresUtc = DateTime.UtcNow.AddMonths(1)
};
// to register the cookie to the browser
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, props).Wait();
return RedirectToAction("UserProfile");
}
}
ModelState.AddModelError(string.Empty, "Invalid Login Attempt");
}
return View(CurrentUserLog);
}
Here is all the problem. I get data from the cookie when I logIn for the first time with the first creation of the cookie as shown in
the code above. However, I can't get the same date from the same
cookie when I stop debugging and run the app again, and redirect to
UserProfile directly without logIn, although the cookie "RememberMecookie" still exists.
Controller/UserProfile
[Authorize]
public async Task<IActionResult> UserProfile()
{
// all lines of code below are working just with the first creation of the cookie with the first login. but if rerun the app again, they all return null if redirect here directly without logIn.
string userId = User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
Claim v = HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
AppUser _user = await userManager.GetUserAsync(HttpContext.User);
string cookieValueFromReq = Request.Cookies["RememberMecookie"];
// this is for normal login without remember me functionality
//AppUser user = await userManager.GetUserAsync(User);
return View(/*user*/);
}
Thanks For all guys who spent time checking out my question. I finally found the problem. This code is really great and it can be a good reference for remembering me functionality using cookie-based Authentication. And there is no problem with the code itself.
The problem was with my Startup file
It was like this
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
//services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews();
The problem was using MVC and AddControllersWithViews together. I didn't know that would make a problem.
However, It should be like this -- using AddControllersWithViews
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => {
options.Cookie.Name = "RememberMeBlogAcademy";
options.LoginPath = "/Account/LogIn";
//options.LogoutPath = "/Home/Index";
//options.AccessDeniedPath = "AccessDenied";
options.ExpireTimeSpan = TimeSpan.FromDays(30);
options.SlidingExpiration = true; // the cookie would be re-issued on any request half way through the ExpireTimeSpan
//options.Cookie.Expiration = TimeSpan.FromDays(5);
options.EventsType = typeof(CookieAuthEvent);
});
services.AddScoped<CookieAuthEvent>();
services.AddControllersWithViews(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Moreover, You don't need to retrieve data from the cookie as shown in Controller/UserProfile above.
Also, when I made debugging to check out the code I tested logout to make sure I really retrieve users data from the cookie not from UserManager and It really works well.
Here is the additional code of logOut
[Authorize]
public async Task<IActionResult> Logout()
{
await signInManager.SignOutAsync();
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Index", "Home");
}

Pass Query String Into Register Form

I'm going to have a subscription flow as follows:
User goes to pricing page, chooses plan, is redirected to ../Register?planId=1
User registers with username and password, is forwarded to billing page, which needs to be ../Subscription/Billing?planId=1
I added an integer, planId, to my RegisterViewModel
On pricing page, I have the links working correctly.
For register controller, I have:
[AllowAnonymous]
public ActionResult Register(RegisterViewModel model, int planId)
{
if (Request.IsAuthenticated) {
return RedirectToAction("Pricing", "Home");
}
RegisterViewModel model1 = new RegisterViewModel();
model1.planId = Convert.ToInt32(Request.QueryString["planId"]);
return View(model1);
}
And in Register view I have:
#Html.HiddenFor(m => m.planId)
However, this value is blank everytime I've run the application. If I can get the planId to be included as part of the register form submission, then I think I can redirect the controller to "../Subscription/Billing?planId=1" after registration.
Here's the current register post controller, where I think I just need to add the planid to the redirectToAction:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, Role = "Admin", ReportsTo = "", ActiveUntil = DateTime.Now.AddDays(-1) };
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("Pricing", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
My question is, how do I get the planid to pass from the query string to a hidden field in the login form?
You can try model1.planId = planId

Have different version of SendEmailAsync

I am using the default ASP.NET MVC, Identity template... I want to send a confirmation email to my clients.
The default implementation which comes with a new project template, has a Register Method in AccountController.cs
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 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);
string message = "Please confirm your account by clicking here";
await UserManager.SendEmailAsync(user.Id, "Confirm your account", HttpUtility.UrlEncode(message));
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
There is a call to UserManager.SendEmailAsync, now this method is defined in Microsoft.AspNet.Identity and I don't want to change it.
The actual send email function is in IdentityConfig.cs
public class SendGridEmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("info#mycompany.com", "DX Team"),
Subject = message.Subject,
PlainTextContent = message.Body,
HtmlContent = message.Body
};
msg.TemplateId = /* I want to pass templateId here */
msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass Username here */);
msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass confirm url here */);
msg.AddTo(new EmailAddress("info#mycompant.com", "Test User"));
var response = await client.SendEmailAsync(msg);
}
}
Now as you see, I am using Sendgrid to send email... so I don't want a message.body to email... I have made some templates and I I want to pass teplate Id with some substituation tags, like username to be replaced in the template.
So I don't want this generic SendAsync method... I want something like
SendGridAsync(SendGridMessage message)
Is it possible to add this method, so I can choose when to call SendAsync and when to call SendGridAsync?
You don't need to use the built in mail service, especially when you want to do something that's a little more complicated.
Define your own messaging service:
public interface IMyMessageService
{
Task SendConfirmationMessage(string confirmUrl, string to)
// define methods for other message types that you want to send
}
public class MyMessageServie : IMyMessageService
{
public async Task SendConfirmationMessage(string confirmUrl, string to)
{
var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("info#mycompany.com", "DX Team"),
Subject = message.Subject,
PlainTextContent = message.Body,
HtmlContent = message.Body
};
msg.TemplateId = /* I want to pass templateId here */
msg.Personalizations[0].Substitutions.Add("confirmurl", confirmUrl);
msg.AddTo(new EmailAddress(to, "Test User"));
var response = await client.SendEmailAsync(msg);
}
}
Register IMyMessageService in your DI framework, and inject it into the controller where the emails are being sent from (e.g. the AccountController).
Now, your register action would look like this (assumes I've injected IMyMessageService and have an instance in _myMessageService):
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 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);
// USE YOUR MESSAGE SERVICE
await _myMessageService.SendConfirmationMessage(callbackUrl, user.Email);
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}

Add role to AspNetUserRoles

Well im kind wasting too much time trying to accomplish this objective and im kinda frustrated, what i want to do is something like that:
**table aspNetUserRoles
userId
roleId
table aspNetRole
roleId
RoleName**
i already did the first part i already filled the aspNetUserRoles with the seed method and have some roles in my database, and i already created a dropdownlist with all my roles, but i have a problem when i press Register i get a error : "UserId not found" i will show here what i alrady did and will place my 2 tables
what i did
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, Sobre = model.Sobre, Idade = model.Idade, Telemóvel = model.Telemóvel, Nome = model.Nome };
var result = await UserManager.CreateAsync(user, model.Password);
var roleStore = new RoleStore<IdentityRole>(db);
var roleManager = new RoleManager<IdentityRole>(roleStore);
var userStore = new UserStore<ApplicationUser>(db);
var userManager = new UserManager<ApplicationUser>(userStore);
userManager.AddToRole(user.Id, "Name");
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);
}
if someone can me explain what i am doing wrong i appreciate a lot :)
use Like this
if (result.Succeeded)
{
userManager.AddToRole(user.Id, "Name");
}

Asp.net Identity - token is not matching encoding issue?

I am trying to use asp.net identity for authentication, I am having some issues with encoding/decoding.
User clicks on forgot password link, so we call out:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[PassModelStateToTempData]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
logger.Info("reset_password attempting for {0}", model.Email);
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
this.Flash("Please check your email, we have sent you instructions on how to reset your password");
return RedirectToAction("ForgotPassword");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
logger.Debug("forgot_password code {0}", code);
var callbackUrl = Url.Action("ResetPassword", "Session", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
messagingService.ResetPassword(user.Email, callbackUrl);
this.Flash("Please check your email, we have sent you instructions on how to reset your password");
logger.Debug("remind_me successfully send out email to {0} {1}", model.Email, callbackUrl);
return RedirectToAction("ForgotPassword");
}
logger.Info("reset_password failed for {0}", model.Email);
// If we got this far, something failed, redisplay form
return RedirectToAction("ForgotPassword");
}
User gets email then clicks link so we run:
[HttpGet]
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
if (code == null)
{
this.Flash("Invalid login token, please enter your email address again");
return RedirectToAction("ForgotPassword");
}
var vm = new ResetPasswordViewModel
{
Code = code
};
return View(vm);
}
We pass on token into view - we ask for email and password, then user hits post and we run:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return RedirectToAction("ResetPassword");
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
logger.Info("reset_password user not found [{0}]", model.Email);
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Session");
}
var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Session");
}
AddErrors(result);
return RedirectToAction("ResetPassword", new { code = model.Code });
}
For some reason tokens seem to not match, here are an example of the token I am getting - why the case difference?
Token:
2015-10-14 13:06:52.7545|DEBUG|Controllers.Application|forgot_password code BoUZZ9OS7rEkKMkEJzerWdds4dZLHFTHO/EkjQC2Zr8YJvCyjsXUKBRLZk8jmAqhjyxOzgqOLdJ8P/ji8y+om2ne7bcsLICzcdLSHzrP6BNEr1/+HKvHcYan+JzAX7Ifpgq7casmMj4f9esAdxejLA==
Notice the case difference:
2015-10-14 13:07:29.7164|INFO|Controllers.Application|reset_password attempting for my.email#gmail.com with token: bouzz9os7rekkmkejzerwdds4dzlhftho/ekjqc2zr8yjvcyjsxukbrlzk8jmaqhjyxozgqoldj8p/ji8y+om2ne7bcsliczcdlshzrp6bner1/+hkvhcyan+jzax7ifpgq7casmmj4f9esadxejla== -> Invalid token.
Your MVC routing is set up to generate lowercase URLs:
routes.LowercaseUrls = true;
This means that your codes are also being converted to lowercase. Possible solutions are:
Turn off LowercaseUrls if you can (or want)
Use MVC attribute routing, though this can be quite a switch.
The simplest option for you may be to simply create the URL yourself:
//Generate the URL without the code parameter
var callbackUrl = Url.Action(
"ResetPassword",
"Session",
new { userId = user.Id },
protocol: Request.Url.Scheme);
//Manually add the code, remembering to encode it
callbackUrl = callbackUrl + "&code=" HttpUtility.UrlEncode(code);

Resources