Step 3: Build email verification

Note

This is a continuation step of the Getting started with ASK on ASP.NET Core MVC walkthrough. To see the concepts explained here in action, copy over the code blocks below to the relevant files/folders of the SuperCRM project developed in the earlier step, or else you can get the previous step code from here.

Why verify?

Verifying email is a necessity to ensure that a user can recover his account if he forgets the password, and that he hasn’t accidentally entered an incorrect email which would mean the recovery email can end up in someone else’s inbox, exposing user’s entire account.

For the above critical reasons, email verification workflow must ensure:

  • Access to the account is limited to only a few operations until the user verifies the email.
  • Every time user changes the email, he must verify it before continuing.

ASK’s user verification feature helps you enforce the above requirements.

Send verification email upon sign up

Here we add send verification email to the SignUp operation we built in the previous step. We also show a simple yet working logic to send the verification email via Gmail.

Tip

ASK’s Starter and Premium source packages come with a modular, template-based email component with support for multiple providers including Gmail, Outlook, Mailgun, SendGrid etc. (new providers can be added in code or via config by providing SMTP network details (say for Amazon SES) and credentials).

Verify template from premium source package

Add the SendVerificationMailAsync call to the SignUp action method in the UserController:

using System;
using System.Net;
using System.Net.Mail;
...
[AllowAnonymous]
[HttpPost, ValidateAntiForgeryToken]
public async Task<ActionResult> SignUp(RegisterModel model)
{
	...
	if (await this.UserService.CreateAccountAsync(dbUser))
	{
		await SendVerificationMailAsync(dbUser);
		await this.authSessionProvider.LoginAsync(model.Email, model.Password, false, this.SecuritySettings.LetSuspendedAuthenticate, true);
	...
}

private async Task SendVerificationMailAsync(DbUser user)
{
	// to use Gmail, you need to enable "Less secure app access" etc. for more information, visit https://support.google.com/a/answer/176600?hl=en#zippy=%2Cuse-the-restricted-gmail-smtp-server%2Cuse-the-gmail-smtp-server
	var username = "<YourGMailSmtpUsername>";
	var password = "<YourGMailSmtpPassword>";
	var host = "smtp.gmail.com";
	var verificationUrl = Url.Action("Verification", "User", new { id = user.VerificationToken }, Request.Scheme);

	var mail = new MailMessage { From = new MailAddress(username) };
	mail.To.Add(user.Username);
	mail.Subject = "Verify your email";
	mail.Body = [email protected]"<p>Hi {user.Name},<br/>Please click the link below to verify your email.<br/><a href='{verificationUrl}'>{verificationUrl}</a><br/>Thank you!</p>";
	mail.IsBodyHtml = true;

	var smtp = new SmtpClient(host, 587)
	{
	Credentials = new NetworkCredential(username, password),
		EnableSsl = true
	};

	await smtp.SendMailAsync(mail).ConfigureAwait(false);
}
...

Verification Operations

Add the below action methods to the UserController:

[VerificationNotRequired, SkipActivityAuthorization]
public ActionResult Verify()
{
	return View();
}

[HttpPost, ValidateAntiForgeryToken]
[VerificationNotRequired, SkipActivityAuthorization]
public async Task<ActionResult> ResendVerificationEmail()
{
	if (this.UserService.IsAuthenticated && this.UserService.IsVerified)
	{
		return RedirectWithMessage("Index", "Home", "Account already verified", OpResult.Success);
	}

	await SendVerificationMailAsync(this.UserService.CurrentUser);
	return RedirectWithMessage("Verify",
		string.Format("We have sent you a mail at {0} – please make sure you follow the link in the mail and verify your email. Please check your email now. It may take a while to reach your inbox.", this.UserService.CurrentUsername), OpResult.Success);
}

[AllowAnonymous]
public async Task<ActionResult> Verification(string id)
{
	switch (await this.UserService.VerifyAccountAsync(id))
	{
		case OpResult.Success:
			return RedirectWithMessage("Index", "Congratulation! Your account has been successfully verified!.", OpResult.Success);
		case OpResult.AlreadyDone:
			return RedirectWithMessage("Index", "Home", "Account already verified", OpResult.Success);
		default:
			return RedirectWithMessage("Verify", "Verification was not successful; please try again.", OpResult.SomeError);
	}
}

Verify operation is to show the email verification required prompt page upon sign up/sign in. It has an option to resend the email and for that we have ResendVerificationEmail operation. It should also enable user to modify the email address if he so wishes from the account settings section (to fix any typo/etc. in the provided email address).

Tip

Account settings operations are out of the scope of this tutorial. However, with both Starter and Premium source packages you get end-to-end implementation of these operations as source code to change username (email), password, basic details and even more in the Premium package.

In ResendVerificationEmail operation, we only resend if the user isn’t verified yet.

The Verification operation is the one that gets invoked when the user clicks on the link in the email. It must match with user’s VerificationToken property (in case you want to re-generate the token, you should use GenerateVerificationToken method).

If VerifyAccount doesn’t find a user by the specified token, verification fails and we redirect the user to the verify prompt page. Otherwise, it updates user’s verified status to true and the restriction on the user to use most functions of the web app is lifted automatically.

Important

As we mentioned in the previous step, the zero-trust nature of ASK’s security pipeline considers all checks as necessary for every action, and gives you extensive options to then selectively opt out actions from checks not needed. We’ve seen a few of such options being used in the above operations as follows:

Note

You see use of multiple overloads of RedirectWithMessage – these response utilities are a part of ASK’s MVC template you’ve got. These utilities streamline secure and rapid development with proper, graceful error handling. Starter and Premium source packages have even more extensive version of these routines, out of which a basic form has been extracted into the MVC template we’re using for this tutorial.

Views and configuration

a. Update the Configure method of the ASPSecurityKitConfiguration class as follows. More on VerificationUrl setting in the next section.

Add the below setting:

settings.VerificationUrl = "/User/Verify";

Remove the below setting to enable the verification check (the default is true so you don’t need to explicitly set it):

settings.MustHaveBeenVerified = false;

b. Add Verify.cshtml view under the Views\User folder. After this you can run the app and verify all the changes of this step.

@using SuperCRM.Infrastructure
@{
	ViewBag.Title = "Verify your Email";
}

<section class="form">
	@using (Html.BeginForm(new { Action = "ResendVerificationEmail" }))
	{
		@Html.AntiForgeryToken()

		<div class="form-group">
			<p>
				If you haven't received a verification email, please click below to re-send it. If you still don't receive a mail after trying multiple times, please make sure it is spelled correctly.
			</p>
			<p>Send verification mail to: @Context.UserService().CurrentUser.Username</p>
			<br/>
			<button type="submit" class="btn btn-primary">Send</button>
		</div>

	}
</section>

Auto redirecting user to verify prompt page

If an operation – not opted out of verification (using attributes such as given in the previous section) – is invoked with an unverified user identity, the security pipeline disallows the request to proceed. From here the MvcSecurityFailureResponseHandler takes over and redirects the user to the verification page if configured in the security settings.

Modify the Configure method in the ASPSecurityKitConfiguration class, initializing the setting:

public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	...
	settings.LoginUrl = "/User/SignIn?returnUrl={0}";
	settings.VerificationUrl = "/User/Verify";
	...
}

Get it without writing a line of code

Both Starter and Premium source packages come with complete implementation of email verification operations including the UI, actions, email template and support for multiple SMTP email providers (MailGun/SendGrid/Amazon SES/Gmail/etc.) as source code, so you don’t have to write them yourself. These are generally more robust and modular. For instance, a proper handling of error in sending email and showing a user friendly message.

Download the sample

Refer the working sample project for this step of the tutorial on GitHub. Download the tutorial repo containing a stand-alone sample project for each step to try it locally on your PC.

Live demo

Visit https://SuperCRM-Mvc.ASPSecurityKit.net to play with a live demo built based on this tutorial.