Updated version of this post can be found here.
At last month’s Nashville .Net Users Group meeting Michael McCann when over some of the aspects of ASP.NET’s membership provider (non-core version). One of the things he talked about was enabling email as part of the user sign up process and for use in password recovery. This post is going to cover the same emailing aspect but in ASP.NET core using mailgun to actually send emails.
Account Controller
In the account controller most of the code needed is already present and just needs to be uncommented. In the Register function uncomment the following which will send the user an email asking that the address be confirmed. This of course stops users from signing up with email addresses they don’t actually have access to.
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); await _emailSender.SendEmailAsync(model.Email, "Confirm your account", "Please confirm your account by clicking this link: <a href=\"" + callbackUrl + "\">link</a>");
And then comment out this next line which would sign the user in before they have used the email above to confirm their account.
//await _signInManager.SignInAsync(user, isPersistent: false);
Next in the Login function add the following bit of code just before the call to _signInManager.PasswordSignInAsync. This looks up the user by email address and returns an error if the account has not been confirmed.
var user = await _userManager.FindByNameAsync(model.Email); if (user != null) { if (!await _userManager.IsEmailConfirmedAsync(user)) { ModelState.AddModelError(string.Empty, "You must have a confirmed email to log in."); return View(model); } }
The last change is in the ForgotPassword function. Uncomment the following code to send the user an email to reset their password.
var code = await _userManager.GeneratePasswordResetTokenAsync(user); var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); await _emailSender.SendEmailAsync(model.Email, "Reset Password", "Please reset your password by clicking here: <a href=\"" + callbackUrl + "\">link</a>"); return View("ForgotPasswordConfirmation");
Forgot Password View
In ForgotPassword.cshtml uncomment the following section to show the UI associated with email based password reset.
<form asp-controller="Account" asp-action="ForgotPassword" method="post" class="form-horizontal" role="form"> <h4>Enter your email.</h4> <hr /> <div asp-validation-summary="ValidationSummary.All" class="text-danger"></div> <div class="form-group"> <label asp-for="Email" class="col-md-2 control-label"></label> <div class="col-md-10"> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <button type="submit" class="btn btn-default">Submit</button> </div> </div> </form>
Caution for existing sites
With the changes above if a user has not confirmed their email address then they will not be able to log in or reset their password. Any existing users would need to have their accounts marked as confirmed manually by updating the EmailConfirmed bit field in the AspNetUsers table or be provided away to confirm their account.
Mailgun
Mailgun is an email service that provides a simple API for sending emails and allows up to 10,000 emails to be sent free every month. I have only used mailgun for sending test emails so I can’t speak to how it holds up at scale.
After signing up for an account click on the domains tab and select the only existing active domain which should start with something like sandbox.
Storing Configuration
In my project I created an EmailSettings class that will be loaded from user secrets in the start up of the application. For more details on general configuration in ASP.NET Core check out this post and thenthis post for more details on user secrets. The following is my email settings class.
public class EmailSettings { public string ApiKey { get; set; } public string BaseUri { get; set; } public string RequestUri { get; set; } public string From { get; set; } }
If using mailgun the above fields map to the following from the mailgun domain page.
EmailSettings | Mailgun | Example |
---|---|---|
ApiKey | API Key | key-* |
BaseUri | API Base URL | https://api.mailgun.net/v3/ |
RequestUri | API Base URL | sandbox*.mailgun.org |
From | Default SMTP Login | postmaster@sandbox*.mailgun.org |
A couple of notes to the above table on what I actually saved in my config files.
EmailSettings Field | Note | Example |
---|---|---|
ApiKey | Used with basic auth and needs username | api:key-* |
RequestUri | Needs the API end point to call | sandbox*.mailgun.org/messages |
The following is what my actual config files ends up looking like.
{ "EmailSettings": { "ApiKey": "api:key-*", "BaseUri": "https://api.mailgun.net/v3/", "RequestUri": "sandbox*.mailgun.org/messages", "From": "postmaster@sandbox*.mailgun.org" } }
In the ConfigureServices function Startup.cs I added a reference to the new settings class so it would be available for dependency injection.
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));
Message Services
In the Services folder there is a MessageServices.cs file which contains the AuthMessageSender class that has an empty implementation for sending email base on an IEmailSender interface which defines a single SendEmailAsync method. This function is already being called in the code that was uncommented above so I am going to use it to call mailgun’s API.
First I need to get the email settings defined above injected into the AuthMessageSenderClass by adding a class level field and a constructor. The only thing the constructor is doing is saving a reference to the injected settings class.
private readonly EmailSettings _emailSettings; public AuthMessageSender(IOptions<EmailSettings> emailSettings) { _emailSettings = emailSettings.Value; }
Next is the SendEmailAsync function mentioned above which I changed to an async function and added the code to send an email using mailgun’s API.
public async Task SendEmailAsync(string email, string subject, string message) { using (var client = new HttpClient { BaseAddress = new Uri(_emailSettings.BaseUri) }) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(_emailSettings.ApiKey))); var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("from", _emailSettings.From), new KeyValuePair<string, string>("to", email), new KeyValuePair<string, string>("subject", subject), new KeyValuePair<string, string>("text", message) }); await client.PostAsync(_emailSettings.RequestUri, content).ConfigureAwait(false); } }
This code is using the HttpClient to send a request to mailgun’s API using basic authorization and form url encoded content to pass the API the relevant bit of information.
With that your application will now email account conformations and password resets.
Other Email Options
Mailgun is obviously not the only option for sending emails. This post from Mashape lists 12 API providers. In addition SMTP is also an option which this post by Steve Gordon covers.