ASP.NET Web API – Identity and forgot password

In previous posts, we’ve got familiar with ASP.NET Web API and ASP.NET Identity. We can use ASP.NET Identity with multiple types of clients such as the desktop app, web app or mobile app for registering and authenticating users. The user will open an account with his email and password and then authenticate himself with services with registered info. If we don’t want to force user enter login data each time he accesses our services, we can get a token and use that token as the authentication key for any call to the server. Today I would like to extend our ASP.NET Web API with “Forgot Password” function so that user can reset his password in case he doesn’t remember it anymore.

1. Prerequisites

I warmly recommend reading these following posts again before getting start with building up new features in our services.
C# – ASP.NET Web API and ASP.NET Identity
Android – Introduction to ASP.NET Identity
I use Mailgun as email service for sending reset code to user’s email. You can use your own email provider by replacing code for sending the email with SMTP. It’s pretty easy.

2. Forgot password

We all know how a “Forgot password” in web app works. We go to the websites, say “Hey, I forgot my password. Let’s me reset it”. The websites will email us a link (reset code would be embedded in this link) which we can click on and enter a new password. In ASP.NET Web API, we can do in that way. However integrating that website callback link into a mobile app is not so easy. We have to embed a web browser component, show the website, check if the user enters a new password or cancel the action (difficult part). So I would like to implement “Forgot Password” as following steps.

1. The user will trigger “Forgot password” with his identity (in our case, his email).
2. We’ll email his reset code.
3. He’ll use that reset code for changing his password.
4. He can now log in with his new password.

2.1 Forgot password action

To let user trigger “Forgot password”, we have to create an action in AccountController. The action should be accessible with an anonymous call because the user doesn’t have his password anymore.

[HttpPost]
[AllowAnonymous]
[Route("ForgotPassword")]
public async Task<IHttpActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
	if (ModelState.IsValid)
	{
		var user = await UserManager.FindByNameAsync(model.Email);
		// If user has to activate his email to confirm his account, the use code listing below
		//if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
		//{
		//    return Ok();
		//}
		if (user == null)
		{
			return Ok();
		}

		// 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.GeneratePasswordResetTokenAsync(user.Id);
		await UserManager.SendEmailAsync(user.Id, "Reset Password", $"Please reset your password by using this {code}");
		return Ok();
	}

	// If we got this far, something failed, redisplay form
	return BadRequest(ModelState);
}

The action receives a ForgotPasswordViewModel as parameter. This model contains only the email of a user. If user hasn’t been found, we just give Ok() result back. If you want you can give another informative message back. However, it’ll give hacker hints to get the correct user from our database.

public class ForgotPasswordViewModel
{
	public string Email { get; set; }
}

2.2 EmailService

The ForgotPassword function will email the reset code to our users. To make SendEmailAsync works, we have to create a EmailService and set it for UserManager.

public class EmailService : IIdentityMessageService
{
	public Task SendAsync(IdentityMessage message)
	{
		const string apiKey = "key-ef7a2525b9a4141408b40cd4d4e438e0";
		const string sandBox = "sandbox5c2ed57ac7b94f0ea5d372f3194b026c.mailgun.org";
		byte[] apiKeyAuth = Encoding.ASCII.GetBytes($"api:{apiKey}");
		var httpClient = new HttpClient { BaseAddress = new Uri("https://api.mailgun.net/v3/") };
		httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
			Convert.ToBase64String(apiKeyAuth));

		var form = new Dictionary<string, string>
		{
			["from"] = "postmaster@sandbox5c2ed57ac7b94f0ea5d372f3194b026c.mailgun.org",
			["to"] = message.Destination,
			["subject"] = message.Subject,
			["text"] = message.Body
		};

		HttpResponseMessage response =
			httpClient.PostAsync(sandBox + "/messages", new FormUrlEncodedContent(form)).Result;
		return Task.FromResult((int)response.StatusCode);
	}
}

As I mentioned above, I use Mailgun for sending the emails to registered users. You can use your own SMTP server or any other email services. When the email arrives, it looks like as the following image.

Forgot password email

Remember to set EmailService for UserManager, otherwise no email will be sent out. In IdentityConfig.cs, create new instance of EmailService and set it to UserManager.

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
	var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
	// Configure validation logic for usernames
	manager.UserValidator = new UserValidator<ApplicationUser>(manager)
	{
		AllowOnlyAlphanumericUserNames = false,
		RequireUniqueEmail = true
	};
	// Configure validation logic for passwords
	manager.PasswordValidator = new PasswordValidator
	{
		RequiredLength = 6,
		RequireNonLetterOrDigit = true,
		RequireDigit = true,
		RequireLowercase = true,
		RequireUppercase = true,
	};
	manager.EmailService = new EmailService();

	var dataProtectionProvider = options.DataProtectionProvider;
	if (dataProtectionProvider != null)
	{
		manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
	}
	return manager;
}

Note: MailGun service doesn’t allow to send email to any recipient with development api key anymore. Use your own MailGun api key with your authorized recipients to test the function or just rewrite the EmailService to authenticate yourself with SMTP server to send email for Forgot Password function.

2.3 ResetPassword

The user has now the code for resetting his password. We need an action to check this code and receive the new password. So in AccountController.cs, create ResetPassword action receving ResetPasswordViewModel as his parameter.

[HttpPost]
[AllowAnonymous]
[Route("ResetPassword")]
public async Task<IHttpActionResult> ResetPassword(ResetPasswordViewModel model)
{
	if (!ModelState.IsValid)
	{
		return BadRequest(ModelState);
	}
	var user = await UserManager.FindByNameAsync(model.Email);
	if (user == null)
	{
		// Don't reveal that the user does not exist
		return Ok();
	}
	var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);
	if (result.Succeeded)
	{
		return Ok();
	}
	return Ok();
}

Of course, the ResetPasswordViewModel must be sent over a HTTP Post to server.

public class ResetPasswordViewModel
{
	public string Code { get; set; }
	public string ConfirmPassword { get; set; }
	public string Email { get; set; }
	public string Password { get; set; }
}

2.4 Client

The code listing below shows the steps how the client should call the service to reset the password of a user.

private string email = "20131226@hintdesk.com";

public override void Run()
{
	InitializeHttpClient();
	Register().Wait();
	ForgotPasswordToGetCode().Wait();
	ReadCodeFromEmailAndResetPassword().Wait();
	GetToken(newSecurePassword).Wait();
}

private async Task ForgotPasswordToGetCode()
{
	Console.WriteLine("--- Forgot password to get code ---");
	Console.WriteLine();

	Console.WriteLine("Step 2: Get code...");
	responseMessage =
		await
			httpClient.PostAsJsonAsync("api/Account/ForgotPassword",
				new ForgotPasswordViewModel()
				{
					Email = email
				});
	responseContent = await responseMessage.Content.ReadAsStringAsync();
	Console.WriteLine(responseMessage.IsSuccessStatusCode ? "Step 2: Get code...Sucessfully" : responseContent);
}

private async Task GetToken(string password)
{
	Console.WriteLine("Step 3: Get Token...");
	responseMessage = await httpClient.PostAsync("Token", new FormUrlEncodedContent(
		new[]
		{
			new KeyValuePair<string, string>("grant_type", "password"),
			new KeyValuePair<string, string>("username", email),
			new KeyValuePair<string, string>("password", password),
		}
		));
	tokenModel = await responseMessage.Content.ReadAsAsync<TokenModel>();

	if (tokenModel != null && tokenModel.AccessToken != null)
	{
		Console.WriteLine("Step 3: Get Token...Sucessfully");
	}
	else
		Console.WriteLine("Step 3: Get Token...Failed");
}

private async Task ReadCodeFromEmailAndResetPassword()
{
	Console.WriteLine("--- Read code from email and reset password ---");
	Console.WriteLine();

	Console.WriteLine("Step 4: Enter code from your email:");
	string code = Console.ReadLine();

	Console.WriteLine("Step 5: Reset password...");

	responseMessage =
		await
			httpClient.PostAsJsonAsync("api/Account/ResetPassword",
				new ResetPasswordViewModel()
				{
					Code = code,
					ConfirmPassword = newSecurePassword,
					Email = email,
					Password = newSecurePassword
				});
	responseContent = await responseMessage.Content.ReadAsStringAsync();
	Console.WriteLine(responseMessage.IsSuccessStatusCode
		? "Step 5: Reset password...Sucessfully"
		: responseContent);
}

private async Task Register()
{
	Console.WriteLine("--- Register ---");
	Console.WriteLine();

	Console.WriteLine("Step 1: Register...");
	responseMessage =
		await
			httpClient.PostAsJsonAsync("api/Account/Register",
				new RegisterBindingModel()
				{
					Email = email,
					Password = securePassword,
					ConfirmPassword = securePassword
				});
	responseContent = await responseMessage.Content.ReadAsStringAsync();
	Console.WriteLine(responseMessage.IsSuccessStatusCode ? "Step 1: Register...Sucessfully" : responseContent);
}

3. Source code

It’s not difficult to build “Forgot password” feature into ASP.NET Web API Identity. The reset code in the demo above is pretty long, it is not suitable for using in a real application. You can extend the demo by writing your own UserManager and generate a short reset code format so that user can enter easily with his mobile device.
Source code: https://bitbucket.org/hintdesk/dotnet-asp.net-web-api-and-asp.net-identity

7 thoughts on “ASP.NET Web API – Identity and forgot password”

  1. Hi.I am developing an app in which i want to send the edit text value to visual studio via post method.How can i send it .Edit text value should bind with visual studio parameter.Please help me

  2. In your example you are passing ChangePasswordBindingModel, which will fail 100% of time since CurrentPassword would be empty (user forgot password).

    So how do you verify the token and change the password at same time?

    Also you are calling ReadCodeFromEmailAndResetPassword with following line:

    httpClient.DefaultRequestHeaders.Add(“Authorization”,
    $”Bearer {tokenModel.AccessToken}”);

    This is not possible in this scenario since you need to be authorized to get token, which means you need username and password (again user forgot password).

  3. @Rob: You’re right, I made a mistake when writing the post. I mentioned ChangePassword instead of ResetPassword. I edited the post and the workflow. It doesn’t required old password anymore.

Leave a Reply

Your email address will not be published. Required fields are marked *