Adding Users With SharePoint Forms Based Authentication

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 &amp; 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

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:

    1. Ensure that you have defined a field within the control whose ID is “Email.”
    2. In the control’s opening tag, put RequireEmail="True".
    3. 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.

Advertisements
Tagged with: , ,
Posted in SharePoint
15 comments on “Adding Users With SharePoint Forms Based Authentication
  1. Nico Viergever says:

    Great, but I do not understand how you copy the UserName to Email. Itried it with a VB script but it does not work.
    Here are a few buts of code:
    The UserName TextBox:

    The VB code:

    Sub UserToEmail(ByVal sender As Object, ByVal e As EventArgs)
    CreateUserWizard1.Email = CreateUserWizard1.UserName
    End Sub

    Obvioulsy I am a (now desparate) novice.
    Can you help? Thanks.

    • Pete Sterpe says:

      Nico,

      The way I copied the contents of the UserName text box into the hidden Email control was to give the text box an OnBlur event handler. This handler fires when the user leaves the UserName text box; its purpose is to set the Email control to whatever is in the UserName. I wanted this handler to run client-side, that is, in the browser, so I wrote the handler in JavaScript. To get the handler to fire is the tricky part — you can’t do the usual, which is to add “OnBlur=’name_of_handler()'” to the UserName control — UserName is a “runat=’server'” control, so the OnBlur handler wouldn’t run in the browser. Instead, to associate the handler with the control, you put some code in your Page_Load() handler. I was writing in C#, so I did this:

      TextBox UserNameTextBox = (TextBox)TheStep.ContentTemplateContainer.FindControl(“UserName”);
      UserNameTextBox.Attributes.Add(“onblur”, “copyuname()”);

      This says that the control’s OnBlur event should be handled by a JavaScript function named “copyuname().” You embed this function in your page in a <script> tag. Here’s what my handler looked like, including a comment about why I took this approach at all:

      /* Function copyuname() is the onblur event handler for the UserName control.
      * It copies the value of that control into the hidden Email control. This lets us put a value in Email without
      * exposing the control and making the user fill it out — our username is the user’s email, so there’s no need
      * to prompt for it twice.
      *
      * Why have a hidden Email control at all? Couldn’t we just use code to set the Email column in the aspnet_Membership
      * table to whatever value the user types into the UserName control? We could, but without an Email control on the page,
      * the CreateUserWizard won’t send the account verification email — it doesn’t know where to send it.
      */
      function copyuname()
      {
      uname = document.getElementById(”).value;
      email = document.getElementById(”);
      email.value = uname;
      }

      My March 17 posting on Client-Side Javascript event handling talks about this general approach. Let me know if you need more help.

      — Peter

      • Nico Viergever says:

        Thanks for your help, Peter.
        But your code runs into an error:

        BC30108: ‘TextBox’ is a type and cannot be used as an expression

        I am probably asking stupis questions, but -again- I am a complete novice.

      • Nico Viergever says:

        Peter,
        I believe the problem is that I simply do not have the C# knowledge at all to make the complete syntax. So when you talk about the PageLoad() handler, I wonder what the complete syntax will have to look like (this ASP/C# is such an unnatural language for novices). So if it is not too much bother, could you show the complete code grom to ?
        Thanks!

      • Nico Viergever says:

        Latest status:
        Object reference not set to an instance of an object.
        Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

        Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

        Source Error:

        Line 6: {
        Line 7: TextBox UserNameTextBox = (TextBox)CreateUserWizard1.FindControl(“UserName”);
        Line 8: UserNameTextBox.Attributes.Add(“onblur”, “copyuname()”);
        Line 9: }
        Line 10:

        The code I inserted looks like this:

        void Page_Load(object sender, EventArgs e)
        {
        TextBox UserNameTextBox = (TextBox)CreateUserWizard1.FindControl(“UserName”);
        UserNameTextBox.Attributes.Add(“onblur”, “copyuname()”);
        }

        function copyuname()
        {
        uname = document.getElementById(”).value;
        email = document.getElementById(”);
        email.value = uname;
        }

        Any ideas????

      • psterpe says:

        Nico,

        I haven’t seen your whole file, so I’m not sure where you are putting your code. Here’s the general structure you should be using:

        <script runat=”server”>

        void Page_Load(object sender, EventArgs e)
        {
        TextBox UserNameTextBox = (TextBox)MainStep.ContentTemplateContainer.FindControl(“UserName”);
        UserNameTextBox.Attributes.Add(“onblur”, “copyuname()”);
        }

        // Any other C# functions you want go here…

        </script>

        // These next tags are typical of SharePoint pages. Your page might not be exactly like this; I include
        // these for orientation. After these <asp:Content> tags, insert another <script> tag and put
        // the copyuname() JavaScript function inside that.

        <asp:Content ID=”Content1″ ContentPlaceHolderId=”PlaceHolderPageTitle” runat=”server”>
        <SharePoint:EncodedLiteral ID=”EncodedLiteral1″ runat=”server” text=”<%$Resources:wss,login_pagetitle%>” EncodeMethod=’HtmlEncode’/>
        </asp:Content>
        <asp:Content ID=”Content4″ ContentPlaceHolderId=”PlaceHolderSiteName” runat=”server”/>
        <asp:Content ID=”Content5″ ContentPlaceHolderId=”PlaceHolderMain” runat=”server”>

        <script>
        function copyuname()
        {
        // Function body goes here
        }

        </script>

        // The rest of the page content goes here.
        // This is where you have the CreateUserWizard control inside of which you have the UserName and Email textbox controls.

  2. normala says:

    Hi, in your code
    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();
    }

    What does the PJS stands for? sorry rather new with this. appreciate if you can help me to understand. i’m trying to use FBA for my site, i’ve set up profile, membership n role in the web.config and now trying to create a registration form. form is created but i hv no luck with it.

    • psterpe says:

      Whoops! There’s no significance to that “PJS”name — those are my initials. It’s just the namespace name I used for the packaging of the UserProfile.

  3. Lawrence says:

    Peter,

    Could your code be modified to use any authenication other than FBA? Like adding users to the .net user?

    Thanks

    • psterpe says:

      Sorry for the lag, Lawrence. And even sorrier for providing a non-response, but I’m not sure what you’re getting at. Would you mind elaborating a bit?

  4. skhumbuzo mjoji says:

    thanks so much!! the only part i was missing was “MembershipUser userInfo = Membership.GetUser(userID);
    userInfo.IsApproved = true;
    Membership.UpdateUser(userInfo);”

    THANK YOU!

  5. Denise Bice says:

    I have a question regarding the code that adds the profile data.

    I have added code to the bottom of the OnCreatedUser event to create the profile for the new user. When the code is executed, I receive the following error “The user could not be authenticated to the Web site being accessed”. I notice in your code, you are using a profile that is already created where as I am creating in new one. Would this cause this error?

    Any thoughts on why this error is happening would be greatly appreciated.

    • psterpe says:

      Sorry for failing to respond. Alas, I have to say I don’t know why you’re getting that error, but this thread might offer some insight:

  6. idowu ibrahim says:

    Nice one. I really love this. Pls I need to import like 200 users into FBA sql database from an excel sheet. Could kindly assist me in this regards. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: