Some folks have found my blog by searching for “how to add users to SharePoint with Forms Based Authentication.” In my second article on FBA, I mostly pointed to other sites at which people have done a nice job describing this, but I didn’t show the code I used. In case that’d be helpful for someone, I’ll show a few of the highlights here. There is nothing SharePoint-y about adding users through Forms Based Authentication (FBA) — it’s all .NET-y, done most easily with the ASP.NET CreateUserWizard control. What follows are some pointers on how to use that control.
The CreateUserWizard control gives you a way to gather several fields of information about a prospective user and create a database record for that person in the membership table. As Microsoft’s documentation of the control shows, it can do a lot; here I show the basics. What makes the control a wizard is that it allows you to architect a multi-step, multi-page process for gathering factoids in which you don’t need to build the pages or even the inter-page buttons. You use tags to describe what you want, and the control supplies the data entry fields, the navigation, and the processing logic.
Below is a sample usage of the CreateUserWizard control to gather a username and password. In this example, we use an email address as a username (and even enforce this, syntactically, with a regular expression validator). This may look like sausage making, but it isn’t all that involved, and I’ll explain each ingredient. Aside from the page directives at the top that make this behave like a SharePoint page, this is .NET all the way.
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#" Inherits="Microsoft.SharePoint.ApplicationPages.LoginPage" MasterPageFile="~/_layouts/simple.master"%>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<asp:CreateUserWizard runat="server" ID="CreateUserWizard1" OnCreatedUser="CreateUserWizard1_CreatedUser"
RequireEmail="True" DisableCreatedUser="false" OnSendingMail="CreateUserWizard1_SendingMail">
<WizardSteps>
<asp:CreateUserWizardStep runat="server" Title="Register" ID="TheStep">
<ContentTemplate>
<table>
<tr>
<td colspan="2" align="center">
Make an Account</td>
</tr>
<tr>
<td colspan="2" style="color:red">
<asp:Literal ID="ErrorMessage" runat="server" EnableViewState="False"></asp:Literal>
</td>
</tr>
<tr>
<td colspan="2">
User Name & Password</td>
</tr>
<tr>
<td class="ms-formlabel" style="width: 25%;">
Email <font size="-2">(will be your user name)</font></td>
<td class="ms-formbody" style="width: 75%;">
<asp:TextBox runat="server" ID="UserName" Columns="50" />
<asp:RequiredFieldValidator ValidationGroup="CreateUserWizard1" runat="server" ID="UserNameValidator"
ControlToValidate="UserName" Display="Dynamic" ErrorMessage="Email is required. This will be your username." />
<asp:RegularExpressionValidator ValidationGroup="CreateUserWizard1" runat="server"
ID="RegexValidator0" ControlToValidate="UserName" Display="Dynamic" ErrorMessage="Please enter a valid email address."
ValidationExpression="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />
</td>
</tr>
<tr>
<td class="ms-formlabel">
Password</td>
<td class="ms-formbody">
<asp:TextBox runat="server" ID="Password" TextMode="Password" />
<asp:RequiredFieldValidator ValidationGroup="CreateUserWizard1" runat="server" ID="RequiredFieldValidator10"
ControlToValidate="Password" ErrorMessage="Password is required." />
</td>
</tr>
<tr>
<td class="ms-formlabel">
Confirm Password</td>
<td class="ms-formbody">
<asp:TextBox runat="server" ID="ConfirmPassword" TextMode="Password" />
<asp:RequiredFieldValidator ValidationGroup="CreateUserWizard1" runat="server" ID="RequiredFieldValidator13"
ControlToValidate="ConfirmPassword" ErrorMessage="Confirm Password is required." />
</td>
</tr>
<tr>
<td colspan="2">
Personal Information</td>
</tr>
<tr>
<td class="ms-formlabel">
First Name</td>
<td class="ms-formbody">
<asp:TextBox runat="server" ID="FirstName" Columns="32" MaxLength="64" />
<asp:RequiredFieldValidator ValidationGroup="CreateUserWizard1" runat="server" ID="RequiredFieldValidatorFName"
ControlToValidate="FirstName" ErrorMessage="First name is required." />
</td>
</tr>
<tr>
<td class="ms-formlabel">
Last Name</td>
<td class="ms-formbody">
<asp:TextBox runat="server" ID="LastName" Columns="32" MaxLength="64" />
<asp:RequiredFieldValidator ValidationGroup="CreateUserWizard1" runat="server" ID="RequiredFieldValidatorLName"
ControlToValidate="LastName" ErrorMessage="Last name is required." />
</td>
</tr>
<tr>
<td colspan="2">
<asp:CompareValidator ID="PasswordCompare" runat="server" ControlToCompare="Password"
ControlToValidate="ConfirmPassword" Display="Dynamic" ErrorMessage="The Password and Confirmation Password must match."></asp:CompareValidator>
</td>
</tr>
</table>
<asp:TextBox runat="server" ID="Email" Style="visibility: hidden" />
</ContentTemplate>
</asp:CreateUserWizardStep>
<asp:CompleteWizardStep ID="CompleteWizardStep1" runat="server">
</asp:CompleteWizardStep>
</WizardSteps>
<SideBarStyle BackColor="#5D7B9D" BorderWidth="0px" Font-Size="0.9em" VerticalAlign="Top" />
<TitleTextStyle BackColor="#5D7B9D" ForeColor="White" Font-Bold="True" />
<SideBarButtonStyle ForeColor="White" BorderWidth="0px" Font-Names="Verdana" />
<NavigationButtonStyle BackColor="#FFFBFF" BorderStyle="Solid" ForeColor="#284775"
BorderWidth="1px" BorderColor="#CCCCCC" Font-Names="Verdana" />
<HeaderStyle BackColor="#5D7B9D" BorderStyle="Solid" ForeColor="White" HorizontalAlign="Center"
Font-Size="0.9em" Font-Bold="True" />
<CreateUserButtonStyle BackColor="#FFFBFF" BorderStyle="Solid" ForeColor="#284775"
BorderWidth="1px" BorderColor="#CCCCCC" Font-Names="Verdana" />
<ContinueButtonStyle BackColor="#FFFBFF" BorderStyle="Solid" ForeColor="#284775"
BorderWidth="1px" BorderColor="#CCCCCC" Font-Names="Verdana" />
<StepStyle BorderWidth="0px" />
<StepNavigationTemplate>
<asp:Button ID="StepPreviousButton" runat="server" BackColor="#FFFBFF" BorderColor="#CCCCCC"
BorderStyle="Solid" BorderWidth="1px" CausesValidation="False" CommandName="MovePrevious"
Font-Names="Verdana" ForeColor="#284775" Text="Previous" />
<asp:Button ID="StepNextButton" runat="server" BackColor="#FFFBFF" BorderColor="#CCCCCC"
BorderStyle="Solid" BorderWidth="1px" CommandName="MoveNext" Font-Names="Verdana"
ForeColor="#284775" Text="Next" />
</StepNavigationTemplate>
</asp:CreateUserWizard>
Here’s a screenshot of what the code above produces:

Rendered CreateUserWizard control for making a new user account
Now let’s dig into each bit of the code above and talk about why it’s there and what it produces. First up is the opening tag of the CreateUserWizard control, formatted here with plenty of line breaks so you can read it more easily:
<asp:CreateUserWizard runat="server" ID="CreateUserWizard1"
OnCreatedUser="CreateUserWizard1_CreatedUser"
OnSendingMail="CreateUserWizard1_SendingMail"
RequireEmail="True"
DisableCreatedUser="false" >
Of course don’t forget the runat="server," and definitely give the control an ID. You’ll need the ID in any code you write to tweak how things work; I’ll show some of my C# code later in the post. The OnCreatedUser and OnSendingMail attributes specify event handlers, that is, they name C# methods that should be called when these events fire. Here’s a summary of the two events I’m handling:
- OnCreatedUser. When the wizard has succeeded in inserting a database record for the user, this event fires. So what’s left to do once the user’s database record has been created? Nothing is required, so you don’t have to have this handler, but if you’re using the custom profile provider for storing ancillary user information, this is when you’d create the user’s profile record. See CreateUserWizard1_CreatedUser() for a simple example.
- OnSendingMail. You can arrange for the CreateUserWizard control to send an email to the newly created user; you’d most likely make this a welcome message. Just before that welcome email is sent, this event fires. This is another event that you don’t have to handle, but there’s something very useful you can do in the handler, namely, to substitute data values on-the-fly into the otherwise canned text of your welcome email. See CreateUserWizard1_SendingMail() for a simple example.
To get the wizard to send the mail at all, you do three things:
- Ensure that you have defined a field within the control whose ID is “Email.”
- In the control’s opening tag, put
RequireEmail="True".
- Set the control’s MailDefinition properties, being sure that the BodyFileName property points to a file that exists and is readable. You can set the MailDefinition properties declaratively with tags, or you can do it in code. Here’s an example of doing it in code in the Page_Load() handler:
CreateUserWizard1.MailDefinition.BodyFileName = "~/MailFiles/MailFile.txt";
CreateUserWizard1.MailDefinition.From = "peter.sterpe@sympraxisconsulting.com";
CreateUserWizard1.MailDefinition.Subject = "Welcome to the Site";
CreateUserWizard1.MailDefinition.IsBodyHtml = true;
In our case, you might have noticed that the “Email” field is hidden:
<asp:TextBox runat="server" ID="Email" Style="visibility: hidden" />
We’re capturing the username in a field whose ID is “UserName” rather than using the “Email” field for this purpose. Necessary? No. I just wanted any application code to refer to “Username” when that’s what it’s really working with. This value will be used for authentication, so I wanted to call it what it is. To get the wizard to send the welcome mail, there has to be an “Email” field, so that field exists — hidden — and a little JavaScript copies the Username value into it. My post on how to do client-side event handlers in Javascript shows you the code for doing this kind of thing.
Completing our look at the opening tag, the attribute DisableCreatedUser="false" tells the wizard whether the newly created user account should be enabled or disabled. If it is enabled, the user can log in immediately; if the account is disabled, an activation step has to occur before the user can login. In the latter case, you’d typically include a link in the welcome mail that, once followed, causes the user’s account to be activated. This technique provides a little bit of fraud protection — although I can try to impersonate you by registering with your email address, I won’t receive the welcome message and therefore won’t be able to activate the account. To accomplish the activation, you instantitate a MembershipUser object, set its IsApproved property to True, and update it. Here’s a code snippet for that:
// Declare userID as a GUID and set it to the user's internal id value from the aspnet_Membership table. Then use the following
// lines to activate the user's account.
MembershipUser userInfo = Membership.GetUser(userID);
userInfo.IsApproved = true;
Membership.UpdateUser(userInfo);
After the CreateUserWizard opening tag, the control must contain one or more steps, declared with the element, all nested within a WizardStepselement. Each step will correspond to a new page. In our simple example, we define only one step whose id is “TheStep.” The wizard lets you designate a special completion step, using the element, for any final processing. We declare that element but put nothing in it since we have nothing special to do. You could use this step to force the wizard to back up if, say, certain criteria hadn’t been met.
So what does our wizard do with its one step? Gather account registration data using text fields — noting earth-shattering. We put the fields in a 2-column table, one column for field labels and one for the data entry controls. All of this is embedded in a ContentTemplate element. The fields are typical .NET controls such as ; you can use drop-down lists or any other kinds of controls you want. In this example, I have defined some field validators, too, to enforce that required fields have a value and to enforce that the username field looks like an email address. To make everything look SharePoint-like, give the field labels the CSS class “ms-formlabel” and the data fields the CSS class “ms-formbody.” This is all basic .NET stuff described in a million places, so I’m not going to belabor it here.
The remainder of the elements in the CreateUserWizard control specify styles for the various regions of the control’s on-screen appearance, including its buttons. This is all optional; if you’re OK with the defaults, you can omit these elements entirely.
So, that’s all you need to do. No database code, no inter-page navigation — just declare the control and let the .NET framework do the rest.
I did decide to enrich the example a little by handling some events, so let’s look at what those handlers are like. You don’t have to provide a handler for the OnCreatedUser event, but one thing you can do in such a handler is create your new user’s profile record. The database table for the .NET Membership provider, aspnet_Membership, holds just enough info to log a user in; it doesn’t store any related information like the user’s first and last name. (OK, I think that’s a bit streamlined.) To store those pesky factoids, you need the .NET Profile provider. Here’s a simple handler:
void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
TextBox UserNameTextBox = (TextBox)TheStep.ContentTemplateContainer.FindControl("UserName");
// Save profile values common to all kinds of users.
PJS.UserProfile profile = PJS.UserProfile.GetUserProfile(UserNameTextBox.Text);
profile.FirstName = ((TextBox)TheStep.ContentTemplateContainer.FindControl("FirstName")).Text;
profile.LastName = ((TextBox)TheStep.ContentTemplateContainer.FindControl("LastName")).Text;
profile.Save();
}
You also don’t have to handle the wizard’s OnSendingMail event, but one thing you can do in this handler is substitute data values into the text of your otherwise boilerplate welcome email. Here’s some sample email text that you’d store in a file:
<p>
Welcome to the site, <%UserName%>. Click on this link below to activate your membership: <a href="<%ActivationURL%>"><%ActivationURL%></a>
</p>
In the text, anything between is a placeholder for which you can substitute a value before the message goes out. Here’s some sample handler code that substitutes values for the UserName and ActivationURL placeholders:
void CreateUserWizard1_SendingMail(object sender, MailMessageEventArgs e)
{
TextBox UserNameTextBox = (TextBox)TheStep.ContentTemplateContainer.FindControl("UserName");
MembershipUser userInfo = Membership.GetUser(UserNameTextBox.Text);
String activationURL = Request.Url.GetLeftPart(UriPartial.Authority) +
Page.ResolveUrl("~/_layouts/ActivateAccount.aspx?ID=" +
userInfo.ProviderUserKey.ToString());
e.Message.Body = e.Message.Body.Replace("<%ActivationURL%>", activationURL);
}
In an upcoming post, I’ll go into more detail about using a welcome email to get a newly registered user’s account activated.