Get user's email address based on his userID .net identity - c#

I have the following function that re-generate a confirmation email (to complete registration) in case of an expired link.
[AllowAnonymous]
[HttpGet]
[Route("ResendConfirmationEmail")]
public async Task<IHttpActionResult> ResendConfirmationEmail(string userId = "")
{
if (string.IsNullOrWhiteSpace(userId))
{
ModelState.AddModelError("", "User Id is required");
return BadRequest(ModelState);
}
if (userId.GetHashCode() != null)
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(userId);
var callbackUrl = new Uri(Url.Link("ConfirmEmailRoute", new { userId, code = code }));
string subject = "Please confirm your account";
string body = "Please confirm your account by clicking this : link";
SendEmail(?????, callbackUrl, subject, body);
}
return Ok();
}
How can I get the email of the user from the webUsers table based on his userid?

You can fetch user from database using UserManager's FindByIdAsync and then get the email
var user = await UserManager.FindByIdAsync(userId);
var email = user.Email;

Related

Reset Password view isn't loading even though the url contains validation key information on ASP.NET MVC

First of all, i have check other solutions here too but it doesn't seem to solve my problem.
The problem is, everything is working ok. If a user clicks on "Forget Password" and enters his username. He is sent an email with reset password link. Now if he clicks on the url, he is directed to the url but the resetpassword page isn't being loaded, instead the other condition, which is HttpNotFound() page is being loaded. Seriously need to know what is going wrong with my code.
This is my ForgotPassword action
[HttpPost]
public ActionResult ForgotPassword(string username)
{
string message = "";
using (MBNSystemEntities db = new MBNSystemEntities())
{
var userdetails = db.Users.Where(x => x.UserName == username).FirstOrDefault();
if (userdetails != null)
{
string validationKey = Guid.NewGuid().ToString().Substring(0, 8);
string validationPin = Guid.NewGuid().ToString().Substring(0, 4);
SendMail(userdetails.Email, validationKey, "ResetPassword");
UserValidationRequest uvr = new UserValidationRequest();
uvr.UserId = userdetails.UserId;
uvr.ValidationType = 1;
uvr.ValidationExpiryDate = DateTime.Now.AddDays(1);
uvr.ValidationKey = validationKey;
uvr.ValidationPin = validationPin;
uvr.ValidationStatus = 0;
db.UserValidationRequests.Add(uvr);
db.SaveChanges();
message = "Reset Password link has been sent to your email id.";
}
else
{
message = "Account Not Found";
}
}
ViewBag.Message = message;
return View();
}
And this is my ResetPassword action
public ActionResult ResetPassword(string validationKey)
{
if(string.IsNullOrWhiteSpace(validationKey))
{
return HttpNotFound();
}
using (MBNSystemEntities db = new MBNSystemEntities())
{
var user = db.UserValidationRequests.Where(x => x.ValidationKey == validationKey).FirstOrDefault();
if (user != null)
{
ResetPasswordModel model = new ResetPasswordModel();
model.validationKey = validationKey;
return View(model);
}
else
{
return HttpNotFound();
}
}
}
I checked by putting breakpoint. The validationKey is passed null in Forgetpassword(string validationkey) instead of the actual validationkey generated in ForgotPassword action.
This is the email Sample:
your email sent is wrong in terms of passing values
localhost:44338/Accounts/ResetPassword?vk={value}
it is looking for a parameter named vk but it should be validationKey
if you can change your SendEmail function to add validationKey instead of vk it will address your problem
or
in your ResetPassword(string validationKey) you can change the name to
ResetPassword(string vk)

Sending a clickable deep link in email in a web api project

I am trying to invoke a deep link that would be sent to the user in an email.
Its a typical forgot password scenario however instead of resetting the password via a web link , the user would do so in an android application.
Here is how i generate the email .
[HttpPost]
[AllowAnonymous]
[Route("ForgotPassword")]
public async Task<IHttpActionResult> ForgotPassword(ForgotPasswordBindingModel model)
{
if (ModelState.IsValid)
{
var user = await AppUserManager.FindByEmailAsync(model.Email);
if (user == null || !(await AppUserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return Ok();
}
// Send an email with this link
string code = await AppUserManager.GeneratePasswordResetTokenAsync(user.Id);
//var callbackUrl = new Uri(Url.Link("ResetPasswordRoute", new { user.Id, code }));
var callbackIntent = "intent://forgotpassword?code="+code+"#Intent;scheme=challengehunt;package=com.seed.challengehunt;end";
await AppUserManager.SendEmailAsync(user.Id, "Reset Password", "This would only work if you are seeing this email in an android device. Please reset your password by clicking here");
return Ok();
}
return BadRequest(ModelState);
}
This is my email service attached to the user manager
private async Task ConfigureGmailAsync(IdentityMessage message)
{
// Mail message
var mail = new MailMessage()
{
From = new MailAddress("xyz#xyz.com", "Admin"),
Subject = message.Subject,
Body = message.Body,
IsBodyHtml=true
};
// Credentials
var credentials = new NetworkCredential("admin#xyz.com", "xyz");
mail.To.Add(message.Destination);
// Smtp client
var client = new SmtpClient()
{
Port = 8889,
UseDefaultCredentials = false,
Host = "mail.domain.com",
EnableSsl = false,
Credentials = credentials
};
// Send it...
await client.SendMailAsync(mail);
}
This is the email i receive
As you can see the link is not clickable.
Any hints?

Email Confirmation: UserManager.ConfirmEmailAsync Error : Name cannot be null or empty

I am trying to write code for email confirmation during email registration for my website. I have a bit customized User model which is similar to regular User code.
The code for sending the email confirmation is in within the Register action:
userService.InsertUser(user);
var appuser = new ApplicationUser(user); //{ UserName = model.Email, Email = model.Email };
appuser.UserName = model.Email;
//var result = await UserManager.CreateAsync(appuser, 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(appuser.Id);
//string htmlcode = HttpUtility.UrlEncode(code);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = appuser.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(appuser.Id, "Confirm your account", "Please confirm your account by clicking here");
return RedirectToAction("RegisterComplete");
From the code, I got the callbackurl which is like http://localhost:63599/Account/ConfirmEmail?userId=22&code=C3mfUtYqGzVCsrCnE5VDzC0wfsbgP6lMSVgqFk25kym3uGh0%2B%2Bltqyh0VRim1ulbwSRHBzAgUCJ1WceipbRcErddmNJqzkksZN50QO%2FqTRprpgKcW19wX33QtftwCh7zUz%2B01aghCdg8jZ8Ff%2FNVfRvbjyIW0wavN0Ueq3xl6jBmrv%2BrnbtuKLJO%2BuFE2zHF
When I type the code into the browser, it will run the ConfirmEmail action from AccountController:
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(userId, code);
//if (result.Succeeded)
//{
// UserService userService = new UserService();
// long uid = long.Parse(userId);
// User user = userService.GetBy(u => u.UserID == uid).FirstOrDefault();
// user.IsVerified = true;
// userService.Update(user);
//}
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
But the result.Succeeded is false. And result.Error[0] : Name cannot be null or empty.
I cannot find anymore information about this error in this context and therefore I am quite stuck.
If I change the confirmation code, I will get result.Error[0]: Invalid token, this made me think that at least my token has been correct. But I don't what this error is complaining about.
Any help/pointers will be appreciated.

Core 2 Identity - Regenerate EmailConfirmationLink

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);
}

Identity using SendGrid v3 to send transactional template as confirmation email

I'm new to asp.net mvc Identity and SendGrid but would really like to use the functionality of both of them.
I would like to let the user sign up using identity registration form and then use SendGrid v3 to send a template (built in my SendGrid account) as the account registration confirmation email. I've created a Transactional template and have an Api Key.
I have enabled email confirmation in identity:
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit https://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");
I have then set up my sendGrid apiKey and account credentials in the app settings of my web.config so I can use them in my code.
<appSettings>
<add key="SendGridUsername" value="xxxxxxx" />
<add key="SendGridPassword" value="xxxxxxx" />
<add key="SendGridApiKey" value="xxxxxxxxxxxxxxxxxxxxxxxx" />
</appSettings>
I have added this to my EmailService in the IdentityConfig.cs but i'm stuck on where to go from here:
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
// Plug in your email service here to send an email.
var apiKey = WebConfigurationManager.AppSettings["SendGridApiKey"];
var client = new SendGridClient(apiKey);
var from = new EmailAddress("me#us.com", "Me");
var subject = message.Subject;
var to = new EmailAddress(message.Destination);
var email = MailHelper.CreateSingleEmail(from, to, subject, "", message.Body);
await client.SendEmailAsync(email);
}
}
I've also read the following but cannot understand where to implement it:
https://sendgrid.com/docs/API_Reference/Web_API_v3/Transactional_Templates/smtpapi.html
{
"filters": {
"templates": {
"settings": {
"enable": 1,
"template_id": "5997fcf6-2b9f-484d-acd5-7e9a99f0dc1f"
}
}
}
}
Any help on this would be awesome as i'm just not sure where to go from here.
Thanks
You can send Transactional Templates in your emails using this code below:
var apiKey = AppConstants.JuketserSendGridKey;
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage();
msg.SetFrom(new EmailAddress("admin#jukester.com", "Jukester"));
//msg.SetSubject("I'm replacing the subject tag");
msg.AddTo(new EmailAddress(model.EmailTo));
//msg.AddContent(MimeType.Text, "I'm replacing the <strong>body tag</strong>");
msg.SetTemplateId("Your TemplateId here");
var response = await client.SendEmailAsync(msg);
var status = response.StatusCode.ToString();
Edit For Your Other question:
For Email confirmation scenario, you will have to send email to the registered email when a user signs up. Create a verification token and save it in database. That email will contain some link or a button in it. This link or button will have that verification token with it. Once user clicks that link/button, a webapi or an action method will be called in the project, there you will verify the verification code and then update the status of EmailConfirmed in the database.
Following are some code snippets that i have done, they might be of help to you.
The following code creates a verification code and updates the user record in database.
var encryptedToken = Utility.Crypt(user.Email);
var updateStatus = await UpdateVerificationCode(userToAdd, encryptedToken);
The following then passes this verification code to the data that needs to be sent in the email. "paramList" is a list of data.
if (updateStatus)
{
paramList.Add(encryptedToken);
var emailModel = Utility.CreateEmailModel(user.Email, paramList, AppConstants.RegistrationTemplateId, (int)EmailType.Register);
await Helper.SendEmail(emailModel);
}
Now this code, will be attached in the link or button in the email sent to user for email verification. When user clicks that link/button then following web api action method for email verification is called.
public async Task<GenericResponse> ConfirmEmail(SetPasswordBindingModel model)
{
var response = new GenericResponse();
if (model != null)
{
try
{
var user = await _aspNetUserService.GetByEmail(model.Email);
if (user != null)
{
if (!string.IsNullOrEmpty(model.VerificationCode))
{
//if time difference is less than 5 minutes then proceed
var originalKey = Utility.Decrypt(model.VerificationCode);
if (user.Email == originalKey)
{
var emailConfirmationCode = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var result = await UserManager.ConfirmEmailAsync(user.Id, emailConfirmationCode);
if (result.Succeeded)
{
var status = await _aspNetUserService.ResetVerificationCode(model.Email);
if (status)
{
response.Message = AppConstants.EmailConfirmed;
response.IsSuccess = true;
}
}
else
{
response.Message = AppConstants.Error;
response.IsSuccess = false;
}
}
else
{
response.Message = AppConstants.InvalidVerificationCode;
response.IsSuccess = false;
}
}
else
{
response.Message = AppConstants.InvalidVerificationCode;
response.IsSuccess = false;
}
}
else
{
response.Message = AppConstants.NoUserFound;
response.IsSuccess = false;
}
}
catch (Exception ex)
{
//response.Message = AppConstants.Error;
response.Message = ex.Message;
}
}
return response;
}
You can take a look at it, and use it if it helps your needs. Thanks

Categories