MVC 4 Password Recovery - asp.net-mvc

First I am quite new to MVC and I am trying to implement a password recovery functionality for MVC 4. I am implementing this using this technique posted here: Where to find C# sample code to implement password recovery in ASP .NET MVC2
I understood the way it works however there is missing a helper class that I try to implement right now. I am talking about the class: NotificationsHelper.SendPasswordRetrieval(model.Email, this.ControllerContext);
The RetrievePassword acction controller on the controller has a parameter PasswordRetrievalModel model. I guess that this is a class model that connects to db and implements some properties among theme is a string property called Email. Is this correct?
Than, the NotificationsHelper.SendPasswordRetrieval(model.Email, this.ControllerContext); static class implements this static method SendPasswordRetrievla with 2 paramateres: model.Email that is the string property frrom the PasswordRetrievalModel model class, so this will be the user email to which we will send the email. Than the second parameter is this.ControllerContext. What is the point of this parameter what values will contain that are sent to the SendPasswordRetrieval method?
Than I implemented the class like this:
public static class NotificationsHelper
{
public static bool SendPasswordRetrieval(string emailAddress, ControllerContext ctx)
{
try
{
StringBuilder emailMessage = new StringBuilder();
emailMessage.Append("<br />");
emailMessage.Append("Hello,");
emailMessage.Append("You have requested a password recovery.");
emailMessage.Append("<br />");
emailMessage.Append("Please click the link below to change your password: <br />");
emailMessage.Append("<br />");
emailMessage.Append(string.Format("http://www.example.com/Account/Validate?email={0}&token={1}", emailAddress, "**345982374532453435345**"));
emailMessage.Append("<br />");
MailMessage email = new MailMessage();
email.From = new MailAddress("noreplay#example.com");
email.To.Add(new MailAddress(emailAddress));
email.Subject = "domain.com Password Recovery";
email.Body = emailMessage.ToString();
email.IsBodyHtml = true;
SmtpClient smtpServer = new SmtpClient();
smtpServer.Host = "smtp.gmail.com";
smtpServer.Port = 587;
smtpServer.Credentials = new NetworkCredential("username", "password");
smtpServer.EnableSsl = true;
smtpServer.Send(email);
return true;
}
catch (Exception e)
{
Trace.WriteLine(String.Format("Failure to send email to {0}.", emailAddress));
return false;
}
}
}
In the code above I listed the line where the url is formatted, how do I bring there the token using the code #agarcian provided? Is the token coming from the second parameter ControllerContext? If yes how do i get it from there?

Add new column for usertable name it pwdresetTocket, when user request to reset password insert Guid.NewGuid() in pwdresetTocket field for that user, append the same in callback URL
if you don't want to add column to existing table, you can create a new table and map it to user Table.
Then your method looks like this.
public static bool SendPasswordRetrieval(string emailAddress, ControllerContext ctx)
{
try
{
StringBuilder emailMessage = new StringBuilder();
string token = Guid.NewGuid();
// call to a method that will update the table with token
updateUsertablewithResetTocket(tocken);
emailMessage.Append("<br />");
emailMessage.Append("Hello,");
emailMessage.Append("You have requested a password recovery.");
emailMessage.Append("<br />");
emailMessage.Append("Please click the link below to change your password: <br />");
emailMessage.Append("<br />");
emailMessage.Append(string.Format("http://www.example.com/Account/Validate?email={0}&token={1}", emailAddress, token));
emailMessage.Append("<br />");
MailMessage email = new MailMessage();
email.From = new MailAddress("noreplay#example.com");
email.To.Add(new MailAddress(emailAddress));
email.Subject = "domain.com Password Recovery";
email.Body = emailMessage.ToString();
email.IsBodyHtml = true;
SmtpClient smtpServer = new SmtpClient();
smtpServer.Host = "smtp.gmail.com";
smtpServer.Port = 587;
smtpServer.Credentials = new NetworkCredential("username", "password");
smtpServer.EnableSsl = true;
smtpServer.Send(email);
return true;
}
catch (Exception e)
{
Trace.WriteLine(String.Format("Failure to send email to {0}.", emailAddress));
return false;
}
}
once user resets the password, empty the reset token field

Related

How to add JwT token in request header ASP.Net MVC Core 6

I have just started to use Asp.Net Core and I managed to create a mvc project. In This project I have created an API and it is secured with token based authorization.I have also used identity framework for user auhentication. Now I want to consume this API to perform CRUD operations with passing token but have no clear idea how to do that. After searching similar questions what I have tried is generate the token using user credentials (username, password) when user successfully logged in or registered and attach the generated token to header and as far as I know it will be passed through each subsequent request.
First I tried creating a method to call to generate the token after success login or registration. This includes in same controller which used for login and registration.
Token generate method
public string GenerateAuthToken(ApplicationUser applicationUser)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub, applicationUser.Id),
new Claim(type:JwtRegisteredClaimNames.Email, applicationUser.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
I call this after success user login and register,
public async Task<IActionResult> Register(RegisterViewModel registerViewModel)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = registerViewModel.Username,
Email = registerViewModel.Email};
var result = await _userManager.CreateAsync(user, registerViewModel.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
var token = GenerateAuthToken(user);
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new
AuthenticationHeaderValue("bearer", token);
return RedirectToAction("Index", "Home");
}
ModelState.AddModelError("", "User Registration Failed");
}
return View(registerViewModel);
}
When this executed, the token is successfully generated but does not attach the token. I do not know if I am doing any wrong here. But I found someone facing the same issue but has tried different way to achieve this. I think it is the correct way but not sure. Instead of generate the token on success login, have to generate it each api call. According to this solution I created another controller and action to generate the token.
public async Task<IActionResult> GetToken([FromBody] AuthViewModel authViewModel)
{
var user = _context.Users.FirstOrDefault(u => u.Email == authViewModel.Email);
if (user != null)
{
var signInResult = await _signInManager.CheckPasswordSignInAsync(user,
authViewModel.Password, false);
if (signInResult.Succeeded)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration.GetSection("JWT")
["TokenSignInKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(type:JwtRegisteredClaimNames.Sub,authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Email,
authViewModel.Email),
new Claim(type:JwtRegisteredClaimNames.Iat,
value:DateTime.Now.ToUniversalTime().ToString())
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new
SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var stringToken = tokenHandler.WriteToken(token);
return Ok(new { Token = stringToken });
}
return BadRequest("Invalid User");
}}
AuthViewModel
public class AuthViewModel
{
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
I added authViewModel to accept logged user credentials since I don't want add them manually, Then I have created another controller to perform the CRUD same as the above mentioned link Please note that I followed the solution mentioned below that page.
private async Task<string> CreateToken()
{
var user = await _userManager.GetUserAsync(User);
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:7015/Auth");
request.Content = JsonContent.Create(new AuthViewModel{
Email = user.Email, Password = user.PasswordHash
});
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request);
var token = await response.Content.ReadAsStringAsync();
HttpContext.Session.SetString("JwToken", token);
return token;
}
request.Content I added to match my solution since token should be generated using user credentials. But I have no idea how to pass the logged in user's credentials with the request. This does not work. It is not possible to access the user password.
This is how I called the token generate action to perform CRUD. And I use JQuery Ajax to call the GetAllSales endpoint.
public async Task<IActionResult> GetAllSales()
{
string token = null;
var strToken = HttpContext.Session.GetString("JwToken");
if (string.IsNullOrWhiteSpace(strToken))
{
token = await CreateToken();
}
else
{
token = strToken;
}
List<Sale> sales = new List<Sale>();
var client = _clientFactory.CreateClient();
var request = new HttpRequestMessage(HttpMethod.Get,
"http://localhost:7015/api/Sales");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
var apiString = await response.Content.ReadAsStringAsync();
sales = JsonConvert.DeserializeObject<List<Sale>>(apiString);
}
Ok(sales);
}
This does not work. An exception throws
'System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)'
Please suggest me and show me how to achieve this correctly. I am expecting all of your help. Thank you.
System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.IHttpClientFactory' while attempting to activate '_7_ElevenRetail.Controllers.AccessApiController'
This issue means you inject IHttpClientFactory in AccessApiController without registering the service in Program.cs.
Register IHttpClientFactory by calling AddHttpClient in Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();

Email view not found for 'model' in Postal.MVC

I am using Postal.MVC5 in my ASP MVC5 project. Initially i had my smtp configuration in web.config so this piece of code was working just fine and was able to send email successfully.
public async Task<ActionResult>SendEmail(EmployeeViewModel emailModel)
{
string _errorMessage;
emailModel.To = "john#outlook.com";
emailModel.Subject = "Test Subject";
emailModel.Message = "Test Message";
await emailModel.SendAsync();
return Json(new {ErrorMessage = _errorMessage});
}
Now, i need to setup Smtpclient during runtime and followed steps shown in this stackoverflow post: How to Set SMTP Client in POSTAL MVC not in WEB.Config
public async Task<ActionResult>SendEmail(EmployeeEmailViewModel emailModel)
{
string _errorMessage;
emailModel.To = "john#outlook.com";
emailModel.Subject = "Test Subject";
emailModel.Message = "Test Message";
//new code
SmtpClient client = new SmtpClient("smtp.test.com");
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential("secretId", "notSecretPassword");
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Port = 111;
client.EnableSsl = true;
Postal.EmailService emailService = new EmailService(new ViewEngineCollection(), () => client);
await emailService.SendAsync(emailModel);
return Json(new {ErrorMessage = _errorMessage});
}
Now this code throws an error something like this:
System.Exception: Email view not found for EmployeeEmailViewModel. Locations searched:
at Postal.EmailViewRenderer.CreateView(String viewName, ControllerContext controllerContext)
at Postal.EmailViewRenderer.Render(Email email, String viewName)
I do have 'EmployeeEmailViewModel' view in ~/Views/Emails/EmployeeEmailViewModel.cshtml .
Any idea why i am getting this error message or how i can resolve it?
Thanks
Sanjeev

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

How to retrieve loginProvider name ASP.Net MVC Core for ExternalLoginConfirmation method

In the ExternalLoginConfirmation method of the AccountController.cs a new user will be created according to user = new ApplicationUser { UserName = model.Email, Email = model.Email }.
I want to create the new user prepending the login provider name in the UserName property:
user = new ApplicationUser { UserName = provider ?? model.Email + model.Email, Email = model.Email };
My idea is to try to do like that:
var loginProviders = _signInManager.GetExternalAuthenticationSchemes().ToList();
var provider = loginProviders[index].DisplayName.ToString();
How can I select index to return the used loginProvider?
Unfortunately var provider = loginProviders.DisplayName.ToString(); does not work.
For Authentication I'm using
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
});
app.UseGoogleAuthentication(new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"]
});
app.UseMicrosoftAccountAuthentication(new MicrosoftAccountOptions()
{
ClientId = Configuration["Authentication:Microsoft:ClientId"],
ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"]
});
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"]
});
While testing my web site I use my credential.
Letting UserName = model.Email, Email = model.Email turns out to give an error.
I can use the same Email, by setting up
services.AddIdentity<ApplicationUser, IdentityRole>(opts => {
opts.User.RequireUniqueEmail = false;
})
But I can not have the same UserName
Any idea?
Thanks a lot.
In the meantime I've found the solution.
In the method
public async Task<IActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null)
you can get the information about the user from the external login provider by calling
var info = await _signInManager.GetExternalLoginInfoAsync();
You can then add a new user by simply prepending info.LoginProvider in the UserName field like that
var user = new ApplicationUser { UserName = info.LoginProvider + model.Email, Email = model.Email };
Doing that you can use your credentials (you email address) to test multiple login provides avoiding conflics while trying to insert in the dbo.AspNetUsers identical UserName fields.
Hope it helps!

GeneratePasswordToken throws odd error in MVC 5 (asp.net Identity) - Lost Password

I am creating a LostPassword function using the code below. When users enter their email and they get a password with link containing the token to reset. In my controller I have the GenerateTokenPassword method (under WebSecurity), but it is throwing this error:
Error: Cannot convert type 'string' to 'int'
Line: var token = WebSecurity.GeneratePasswordResetToken((foundUserName).ToString(), 1440);
When I debug, I get my username in the foundUserName field and it is a string. The 1440 is the timeout on token expiration, and I am supplying an int to begin with. What int is it looking?
Any ideas what this might be?
Thanks,
Sanjeev
// POST: Account/LostPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult LostPassword(LostPasswordModel model)
{
ApplicationUser user;
using (var db = new MyDbContext())
{
var foundUserName = (db.People.Where(p => p.Email == model.Email).FirstOrDefault().UserName);
user = UserManager.FindByName(foundUserName.ToString());
// user = Membership.GetUser(foundUserName);
//Generate password token that will be used in the email link to authenticate user
WebSecurity.InitializeDatabaseConnection("", "System.Data.SqlClient", "AspNetUsers", "Id", "UserName", false);
var token = WebSecurity.GeneratePasswordResetToken((foundUserName).ToString(), 1440);
// Generate the html link sent via email
var code = UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
string resetLink = "<a href='"
+ Url.Action("ResetPassword", "Account", new { rt = token }, "http")
+ "'>Reset Password Link</a>";
//Email stuff
string emailsubject = "Reset your account password.";
string emailbody = "Please click this link to reset your account password: " + resetLink;
string emailfrom = "mymail#mymail.edu";
string emailto = model.Email;
MailMessage message = new MailMessage();
message.To.Add(emailto);
message.From = new MailAddress(emailfrom);
message.Subject = emailsubject;
message.Body = emailbody;
message.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient();
smtp.UseDefaultCredentials = false;
smtp.Credentials = new System.Net.NetworkCredential("mymail", "mypass");
smtp.Host = "smtp.gmail.com";
smtp.Port = 587;
smtp.EnableSsl = true;
smtp.Send(message);
return View();
}
}
It would appear that the compiler is interpreting the 1440 value as a string, you could try declaring an explicit int variable for it and passing that instead.
Alternatively, given tokenExpirationInMinutesFromNow is an optional parameter which defaults already to 1440 then you can omit it entirely
var token = WebSecurity.GeneratePasswordResetToken((foundUserName).ToString());

Resources