Requiring Users to Confirm their E-mail Address after they Create an Account

Over the past couple of weeks I received a number of e-mails from readers with more or less the same question: how do you require users to confirm their e-mail addresses before they are allowed to log in with an account they just created. Rather than answer them individually by e-mail, I decided to write this article to explain the principles.

Introduction

In this article I’ll show you how you can let users sign up for an account that they need to confirm first before they can sign in with it. By using the standard ASP.NET features for account management (Membership, the CreateUserWizard control, built-in e-mail sending capabilities), it turns out this is pretty easy to do. Here’s a short rundown of all the required steps:

  1. Configure Membership, create a page using the standard CreateUserWizard control that sends out a standard e-mail with the user name and password.
  2. Disable the account when it is is created so users can't log in until they confirm their e-amail address.
  3. When the account is created, generate a unique ID for the user, save it the user’s profile, and then customize the e-mail message by including a link to a confirmation page that includes this unique ID.
  4. When the user clicks the confirmation link in the e-mail, retrieve the unique ID from the query string and use it to validate the account and approve the user.

I’ll dig a little deeper into each step in the following sections. You’ll find the full source code for the demo application at the end of the article. This article was written using Visual Studio 12 and SQL Server LocalDB. However, if you follow the instructions and create a new site from scratch, it would equally work in Visual Studio 2008 and 2010.

1. Setting the Stage

The first thing you need to do is modify your site so users can sign up for an account. This part is pretty simple and is identical to what you would normally do. I won’t describe this part in detail but instead describe the steps I followed to set up the demo site.

  1. Create a new web site or web application in Visual Studio (the source you can download is for a web site project).
  2. Set up Membership by executing the following NuGet command:
    Install-Package Microsoft.AspNet.Providers
  3. Create a page that contains a CreateUserWizard control so users can sign up for an account. This page is called SignUp.aspx in the sample application.

  4. Create an e-mail message body in the App_Data folder that contains text to welcome the user. You can use the standard <% UserName%> and <% Password %> placeholders to inject the user name and password. For example:
Dear <% UserName %>

Your account has been created. Please find your account details below:

Username: <% UserName %>
Password: <% Password %>

This file is saved as ~/App_Data/YourNewAccount.txt in the sample application.

  1. Set up the MailSettings element on the CreateUserWizard control so it uses this mail template like this:
<asp:CreateUserWizard ID="CreateUserWizard1" runat="server" >
<MailDefinition BodyFileName="~/App_Data/YourNewAccount.txt" From="imar@example.com"           Subject="Your new account">
  </MailDefinition>
    <WizardSteps>
<asp:CreateUserWizardStep runat="server" />
    <asp:CompleteWizardStep runat="server" />
  </WizardSteps>
</asp:CreateUserWizard>
  1. Configure the system.net element in Web.config so it can successfully send out e-mail. In the sample application I am using a local pickup folder. (see http://imar.spaanjaars.com/496/using-a-local-pickup-folder-for-email-delivery for more details).
  2. Create a page with a Login control so users can sign in to your site. In the sample application, this page is called Login.aspx.
  3. Test the application. For each account you create you should receive an e-mail. You should be able to log in using the user name and password you supplied.

2. Disabling Created Users

This step is really easy because the CreateUserWizard has built-in support for this. All you need to do is set DisableCreatedUser to True, like this:

<asp:CreateUserWizard ID="CreateUserWizard1" runat="server" DisableCreatedUser="True">

With this setting on, you should no longer be able to log om with new new accounts you create on the site.

3. Inserting a Confirmation Link in the E-mail Message

This action consists of a few steps:

  1. Generate a unique ID for the user and store it in the user’s profile. A good location to do this is in the CreateUserWizard’s CreatedUser event, like this:
    protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
    {
    ProfileCommon userProfile = ProfileCommon.Create(CreateUserWizard1.UserName) as ProfileCommon;
    accountConfirmationKey = Guid.NewGuid().ToString();
    userProfile.AccountConfirmationKey = accountConfirmationKey;
    userProfile.Save();
    }
    For this code to work, I added the following profile property to Web.config:
    <profile defaultProvider="DefaultProfileProvider">
    <properties>
    <add name="AccountConfirmationKey"/>
    </properties>
    <providers>
    <add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
    </providers>
    </profile>
    Notice how the code explicitly creates a profile using ProfileCommon.Create. Since the user account has just been created, Profile is not active for the current page yet so you need to get it explicitly. This also means you need to call Save. The random ID (created using Guid.NewGuid()) is stored in the user’s profile and in a field in the class so you can use it when sending the e-mail, discussed next.

  2. Modify the outgoing message. The CreateUserWizard has a handy SendingMail event you can hook into to customize the e-mail. You access the MailMessage instance on the e.Message property. Here’s the code from the sample application that builds up a link to the current web site, embeds the unique user ID, and then adds the link to the MailMessage’s body:
    protected void CreateUserWizard1_SendingMail(object sender, MailMessageEventArgs e)
    {
    string confirmLink = string.Format("{0}://{1}/Confirm.aspx?ConfirmationKey={2}&UserName={3}", Request.Url.Scheme, Request.Url.Authority, accountConfirmationKey, CreateUserWizard1.UserName);
    e.Message.Body = e.Message.Body.Replace("##ConfirmLink##", confirmLink);
    }
    Note: I could have combined the code in the CreatedUser and SendingMail events into a single block called from SendingMail. However, separating it like this feels a bit cleaner.

  3. Modify the e-mail body to include a placeholder for the link. Here’s the full text for that message from the sample application:
    Dear <% UserName %>,
    
    Your account has been created. However, before you can use it you need to confirm 
    your e-mail address first by clicking the following link: 
    
    #ConfirmLink##
    
    Please find your account details below:
    
    Username: <% UserName %>
    Password: <% Password %>

  4. Change the CompleteSuccessText property of the CreateUserWizard control to inform the user the account is not activated immediately, but needs manual confirmation first. Here’s the full code for the CreateUserWizard:
    <asp:CreateUserWizard ID="CreateUserWizard1" runat="server" DisableCreatedUser="True"
       OnCreatedUser="CreateUserWizard1_CreatedUser" OnSendingMail="CreateUserWizard1_SendingMail"
       CompleteSuccessText="Your account has been successfully created. You'll 
          receive an e-mail with a confirmation link that you need to click to confirm 
          your e-mail address and activate your account."
    >
    <MailDefinition BodyFileName="~/App_Data/YourNewAccount.txt" From="imar@example.com" Subject="Your new account">
    </MailDefinition>
    <WizardSteps>
    <asp:CreateUserWizardStep runat="server" />
    <asp:CompleteWizardStep runat="server" />
    </WizardSteps>
    </asp:CreateUserWizard>

    When users now sign up for a new account, they receive something like the following in their Inbox:

    The e-mail message with confirmation link

4. Activating the User Account

When the user clicks the confirmation link in the e-mail message, he's being sent to the Confirm.aspx page, along with the confirmation ID and UserName in the query string. A typical link could look like this:

http://localhost:6864/Confirm.aspx?ConfirmationKey=ef05127f-a050-41b9-9132-be8a8aeea925&UserName=Imar

The localhost:6864 part varies depending on where you run this code, and would include your full domain name when used on a production site.

In the code behind of Contfirm.aspx, you then get the user name from the query string and use it to create a Profile instance. This profile instance should have a value for the AccountConfirmationKey property that matches the confirmation key sent in the query string. If the two match, you can create a MembershipUser for the requested user name and activate the account by setting IsApproved to true. Here’s the code from the sample application that shows a way you can implement this:

protected void Page_Load(object sender, EventArgs e)
{
string userName = Request.QueryString.Get("UserName");
string accountKey = Request.QueryString.Get("ConfirmationKey"); var profile = ProfileCommon.Create(userName) as ProfileCommon; if (accountKey == profile.AccountConfirmationKey) { var user = Membership.GetUser(userName); user.IsApproved = true; Membership.UpdateUser(user); Status.Text = "Account confirmed successfully."; } else { Status.Text = "Something went wrong while confirming your account."; } }

As you can see, implementing this functionality is pretty straightforward, as most of what you need is supported out of the box. Hooking into some events to customize behavior is a great way to make modifications to existing functionality without building your own security system from scratch.

Extending the Sample

The sample application that comes with this article is pretty simple so it’s easy to focus on the concept without getting confused by too many details. For a real-world website, you may want to implement the following features:

  • Key expiration. Along with the unique ID you could save the date and time the key was generated in the profile as well. Then when the key is validated in Confirm.aspx you could check this date and decide not to activate the user when the key has expired.
  • If you don’t like sending the user name and password over e-mail and the query string, you could leave them out of the mail message. Then in Confirm.aspx you could add a TextBox and a Button so the user can manually enter their user name and click a confirm button. In the Code Behind you would replace the code that gets the user name from the query string with code that gets it from the text box.

Downloads

The full source code for this application (C# only)


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Tuesday, July 24, 2012 7:46:20 AM Marcel said:
Hi Imar,

What's the advantage of creating your own unique key instead of just using the providerUserKey that already exists when creating a new memberhip account?

On Tuesday, July 24, 2012 7:53:35 AM Imar said:
A Marcel,

A couple of reasons:

1. ProviderUserKey should be treated as "internal details"; the correct way to retrieve a user is by the user name and application name. The ProviderUserKey could change in a future implementation of Membership (not that it will ever happen of course, but still).

2. You can't expire this key and have the user request another one. It's directly associated with the account created.

3. I rather not send internal IDs like this over e-mail. Of course I am doing the same with the user name, but you can work around that as explained in the section "Extending the Sample".

4. Other implementations of Membership (such as the Access version: http://imar.spaanjaars.com/404/using-the-microsoft-access-providers-to-replace-the-built-in-sql-server-providers) use an Int instead of a Guid, making it easier to guess the conformation key.

So, lots of reasons to generate your own key.

Cheers,

Imar
On Tuesday, July 24, 2012 8:34:35 AM Marcel said:
Thanks for the explanation. I usually use the providerUserKey for this, but your reasoning makes a lot of sense.
On Thursday, November 29, 2012 11:04:57 AM Quinttin said:
hi imar ,  how to do this :everytime the user login he need to verify his username and get a new password?
On Thursday, November 29, 2012 2:38:44 PM Imar Spaanjaars said:
Hi Quinttin,

Why would you want to do this?

I guess you can use the Membership API to disable the user after each login. Haven't tried it out though....

Cheers,

Imar
On Monday, January 28, 2013 6:50:42 AM kankhara Nirav said:
Hello Imar,

Ur artical so nice and really help for me..
On Monday, March 25, 2013 12:34:31 AM mike said:
what is the html code for the user profile page
On Saturday, August 24, 2013 12:52:39 PM Abdul Rahim Shaik said:
Nice Article, i am looking for this kind of requirement.
On Tuesday, March 25, 2014 8:10:37 AM Gayle Abington Hagerty said:
having no luck in getting this work please advise
Requiring Users to Confirm their E-mail Address after they Create an Account

thanks
On Tuesday, March 25, 2014 8:14:38 AM Imar Spaanjaars said:
Hi Gayle,

Hard to say based on a description of "no luck in getting this work" alone.

May I suggest you post this on a forum such as http://p2p.wrox.com or stackoverflow.com and provide a lot of detail about your setup and the things that aren't working?

Cheers,

Imar
On Wednesday, August 13, 2014 9:04:22 AM wazz said:
another great article.

when we send an e-mail through the log-in tools, the 'From' field must be an e-mail address. i've been trying to find a way of adding a name as well as an address, the way we can in System.Net.Mail:

message.From = new MailAddress("admin@mysite.com", "My Site");

do you know of a way to do that with these controls?
tnx.
On Friday, August 15, 2014 2:18:10 PM Imar Spaanjaars said:
Hi there,

Have you tried assigning a new address to the From property of e.Message in the SendingMail event?

Imar
On Saturday, August 16, 2014 12:45:06 AM wazz said:
sry, i realized after writing that i had not new'ed a MailAddress. i was thinking i could do something like,
e.Message.From = "admin@mysite.com, mysite";

then i realized 'From' is part of a MailAddress:
e.Message.From = new MailAddress("admin@mysite.com", "mysite");

works. tnx.
(p.s. there's a real problem with the "captcha".)
On Saturday, August 16, 2014 6:41:41 AM Imar Spaanjaars said:
The Captcha is session based, so if you have multiple tabs open with articles on my site, it may be validating against the wrong value. It's something I need to fix one day. ;-)

Cheers,

Imar
On Saturday, August 16, 2014 6:55:24 AM wazz said:
ah. that's probably it then. i have a few tabs open from various days/times.
On Sunday, December 27, 2015 9:59:42 AM Sam said:
Hi Imar, Thank you for your informative article.
However, the information in the article does not address the newer ASP.NET Identity for user management. I have been looking for information on how to implement email confirmation with Visual Studio 2015 and it seems that there is no information on how to implement it in WebForms. The Tutorials present only deal with MVC.

How do you confirm user emails using ASP.NET Identity?

Thanks.
On Sunday, January 03, 2016 11:49:39 PM Imar Spaanjaars said:
Hi Sam,

Yes, most of them deal with ASP.NET MVC. However, many of the underlying implementation is the same for Web Forms. Can't you make it work by learning the Identity stuff from the MVC articles and combine it with this (somewhat dated) article?

Cheers.

Imar

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.