Implementing Your IEmailSender For Forgot Password Reset

When you're implementing your Identity Server application in C# you will most likely need to implement some forgot password and reset password functionality.

The usual and best way to implement this is to allow the user to enter an email or username into a Forgot password page -then have Identity Server email a link to the users email (on verification of it being valid).

Starting with the add from scaffold

When you select the scaffold for the forgot and reset password functionality from Identity Server you get a stub for the IEmailSender interface override.

Before we go any further. If you don't know at this point - IEmailSender is the interface provided from Microsoft to override and implement you email send functionality.

If you take a look at the IEmailSender Microsoft WebPage then you see it's include and definition is as follows:

Namespace: Microsoft.AspNetCore.Identity.UI.Services

public interface IEmailSender

As part of your definition, you need to override the following method:

SendEmailAsync(String, String, String)

The inputs for this method are:

  1. String: Email Address
  2. String: Email Title
  3. String: Email Body

Now that we've giving a little definition on what we need to create, let's start to take a look at the code.

One more thing to note. To make sure we have this configurable, we will be putting the email server details into our appsettings.json and reading them into a data model that we will call EmailSettings.

One more thing to note before moving forward. This example will be using a SMTP Client. Microsoft has declared that their System.Net.Mail SmtpClient is no Obsolete, and so we will be using MailKit and MimeKit packages for this example.

You can download these into your project using NuGet from within your DevStudio tool.

The scaffold build stubbed code

If you started with the scaffold and added the forgot and reset email pages from there, you should see a new class and file called EmailSender.cs. If you don't then create this file now. 

The initial autogenerated code should look something like the following:

public interface IEmailSender { 
  Task SendEmailAsync(
    string email, string subject, string message); 
}

public class EmailSender : IEmailSender { 
  public Task SendEmailAsync(string email, string subject, string message) 
  { 
    return Task.CompletedTask; 
  } 
}

This is the code we will be changing to be able to send our email. Before we do this, let's update all the other places in our code we need to, so as to call this Async email sender code.

Adding the email settings

As I mentioned above. We want to be able to update the email settings outside of the actual code, and so appsettings.json is the perfect place.

To achieve this, we need to add a few elements. First, we need to create our data model.

You can do this by either creating a new file called EmailSettings.cs (create this somewhere such as Models or Entities folders). The other option, the one I prefer to use, is to add this data model class into your EmailSender.cs file. I like to keep all the related code together.

However, you want to structure your code is your own personal preference.

The data model I use is as follows: 

public class EmailSettings { 
  public string MailServer { get; set; } 
  public int MailPort { get; set; } 
  public bool UseSSL { get; set; } 
  public string SenderName { get; set; } 
  public string Sender { get; set; } 
  public string Password { get; set; } 
}

You can probably tell from the above, this will include all the details you need to connect to your server. I like to include a useSSL flag in my code (I've seen other examples that don't) as I have used servers such as smtp.office365.com that you need to set the flag to false for it to work - at least in my case.

Next, you need to update your Startup.cs file. Make a change in

Startup.cs > ConfigureServices

services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));

This code gets your configuration from the appsettings.json file and makes it available for you to use.

There is one more change you need to do. At the bottom of ConfigServices you need to add the following line of code:

services.AddSingleton<IEmailSender, EmailSender>();

This will create a singleton using your EmailSender code.

Note: You will need to use dependancy injection to get your email sender into your controller - this will look something like the following:

private readonly ILogger _logger; private readonly IEmailSender _emailSender;
private readonly ILogger<HomeController> _logger;
private readonly IEmailSender<EmailSender> _emailSender;

public HomeController(ILogger<HomeController> logger, IEmailSender<EmailSender> emailSender)
{
    _logger = logger;
    _emailSender = emailSender;
}

Forgot password model

To allow us to pass the package into the EmailSender controller code, we need to create the ForgotPassword model.

First, create a new model called ForgotPasswordModel.cs

Once done add the following code to the class:

public string Email { get; set; } 
public string uUsername { get; set; } 
public string ReturnUrl { get; set; }

These values are required for us to pass in the information we require. Note: ReturnUrl is required if you want to return to the URL that you have your forgot password link.

Webpage definition

The webpage definition can be taken from the default forgot password page. This page simply has to have a field to enter the email address (or username) of the person looking to reset the password, and a submit button.

This page would look something similar to:

@model ForgotPasswordModel
@{
    ViewData["Title"] = "Forgot your password?";
}

<h1>@ViewData["Title"]</h1>
<h4>Enter your email.</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Input.Email"></label>
                <input asp-for="Input.Email" class="form-control" />
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Submit</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

The above is the default page taken from the scaffolding for ForgotPassword. This is just an example for you to look at.

To give some extra info for thought. As part of the button submit - you could use asp-action to ensure your ForgotPassword method is called.

Controller code

If you're using the default scaffold code then you will have the default PageModel with OnPostAsync code. However, if you want to add your action code to the controller, you will need to add the following

public async Task SendEmailAsync(string email, string subject, string message)
{
  try
  {
    var mimeMessage = new MimeMessage();

    mimeMessage.From.Add(new MailboxAddress(_emailSettings.SenderName, 
      _emailSettings.Sender));

    mimeMessage.To.Add(new MailboxAddress(email));
    mimeMessage.Subject = subject;

    mimeMessage.Body = new TextPart("html")
    {
      Text = message
    };

    using (var client = new SmtpClient())
    {
      // For demo-purposes, accept all SSL certificates (in case the server 
      supports STARTTLS)
      client.ServerCertificateValidationCallback = (s, c, h, e) => true;

      if (_env.IsDevelopment())
      {
        // The third parameter is useSSL (true if the client should make an                             
          SSL-wrapped
        // connection to the server; otherwise, false).
        await client.ConnectAsync(_emailSettings.MailServer,                 
          _emailSettings.MailPort, _emailSettings.UseSSL);
      }
      else
      {
        await client.ConnectAsync(_emailSettings.MailServer);
      }

      // Note: only needed if the SMTP server requires authentication
      await client.AuthenticateAsync(_emailSettings.Sender, 
        _emailSettings.Password);

      await client.SendAsync(mimeMessage);

      await client.DisconnectAsync(true);
    }
  }
  catch (Exception ex)
   {
    // TODO: handle exception
    throw new InvalidOperationException(ex.Message);
  }
}

The Forgot Password page controller

As part of this walk-through I'm not going to include any code relating to your forgot password page as this is beyond the scope of this post, and everyone may want to do this different.

I'd suggest to start with the scaffolding and go from there.

This post is about implementing the email sender code to use in your forgot password page; your email sender code. You could also use this code in any other controllers where you want to send emails to a user's email address.

I hope this proves useful in your coding endeavors.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram