Examining ASP.NET's Membership, Roles, and Profile - Part 15By Scott Mitchell
When a visitor registers a new account on an ASP.NET website that uses the Membership system, they are prompted (by default) for their username, password, e-mail address, and other pertinent information. Along with functionality for registering new accounts, the ASP.NET Membership system provides page developers techniques for modifying information about users. For instance, with just a couple of lines of code you can change an existing user's e-mail address, approve a user, or unlock them (if their account was locked out). However, there are certain bits of user information that cannot be modified through the Membership API, such as the username.
For most sites this is a non-issue. Once a visitor has registered an account that username is fixed; if they want a different username, well, they'll just have to register a new account. But consider a website that has customized the account creation process so that instead of prompting the user for both a username and e-mail address, the user is only asked to enter an e-mail address and that it is used as both their username and e-mail address on file. Anytime a user switched e-mail addresses - which can happen when changing jobs, changing ISPs, or moving to the new, hip, web-based e-mail provider of the day - they need to also change their username on your site.
In order to change a user's username we'll need to bypass the Membership API and work directly with the user store. This article shows how to interface directly with the
SQL Server database schema used by the
SqlMembershipProvider to change an existing user's username. Read on to learn more!
Modifying User Account Information
The .NET Framework includes two classes for programmatically working with user accounts via the Membership system. The first class is
Membership, which has methods for creating, updating, and deleting user accounts along with methods for getting information about a particular user. The other class,
MembershipUser, models a user in the Membership system and has properties that describe the user:
LastLoginDate, and so on. To change a user's e-mail address you would use these two classes in the following way:
- Get information about the user via the
'Get information about the currently logged on user
Dim userInfo As MembershipUser = Membership.GetUser()
- Set the various properties of the
MembershipUserobject returned by the
userInfo.Email = "newAddress@example.com"
- Update the modified user account via the
UpdateUsermethods are called depends on the configured Membership provider. As discussed in Part 1 of this series, the Membership system is built using the provider model, meaning that the Membership API - the
Membershipclass - simply defines a set of methods and properties. The actual work is handled by a configured provider. We've been using the
SqlMembershipProviderprovider throughout the course of this article series. As its name implies, the
SqlMembershipProviderprovider uses a SQL Server database as the user store. Consequently, when we call
UpdateUserfrom our code, the provider connects to the specified database and runs a stored procedure that gets information about the requested user or updates the specified user's account.
Ideally, anytime you need to work with the Membership system or examine or modify user account information you should use the
Membership class and avoid going
directly to the underlying database. Working directly with the database ties you to a specific provider implementation. Moreover, working directly with the database is
more error-prone as the Membership API sets up a nice black box on your behalf, has straightforward named properties and methods, and is well documented. The database schema
SqlMembershipProvider provider lacks any sort of documentation and will have you guessing as to what tables and columns store what data.
Unfortunately, idealism and pragmatism only rarely intersect. In some cases - such as allowing a user to change their username - we have no choice but to work directly with
the underlying data store.
|Another Example of Bypassing the Membership API|
To circumvent these checks you need to go directly to the underlying user store and changing the password in the database, bypassing the Membership API. For more information on this particular scenario refer to the "Allowing Administrators to Change Users' Passwords" section in the Recovering and Changing Passwords tutorial, which is one of 14 tutorials in my Website Security Tutorials series.
An Overview of How the
SqlMembershipProvider Provider Stored User Account Information
SqlMembershipProviderprovider is designed so that one database can hold user accounts for several different applications. Each user account is associated with an application. Whenever the Membership system goes to the database to get information about a user or to update a user or to create a user it passes in the application name so that the user is retrieved/updated/created from the appropriate application. The applications that partition the users are maintained in a database table named
aspnet_Applications; each application has an
ApplicationNamefield, but is uniquely identified via the
User account information is stored in two tables:
aspnet_Users- contains a row for each user in the system. Each user is uniquely identified via the
UserIdproperty and each user has an
ApplicationIdvalue that indicates what application the user account belongs to. This table only stores the core user-related fields:
LastActivityDate. There's also a
LoweredUserNamefield that stores the username but in all lowercase letters. This field is present for database configurations that are case sensitive.
- aspnet_Membership - stores additional user information, such as the user's password, e-mail address, whether or not they're approved, their last login date, and so forth.
LoweredUserNamefields in the
Creating a "Change Your Username" Page
To illustrate changing a username I've created a sample application that includes a page named
ChangeUsername.aspx. (This demo application is available for download at the end of this article.) To change his username a user would sign into the site with his current username and then visit
ChangeUsername.aspxand enter the new username. From the end user's perspective, that's all there is to it. Things are a little more complicated for us, the page developer. First, we need to ensure that the new username the user is requesting is not already taken. Next, we must burrow down into the database and update the
LoweredUserNamefields in the
aspnet_Userstable. Finally, we need to "re-sign in" the user with their new username.
Before we jump into the code, let's first talk about this page's user interface. This page needs a TextBox control for the user to enter his new username and a Button control
that, when clicked, will kick off the username change. The following screen shot shows this page when first visited by a user. I placed this page in the
which has a
Web.config that locks down the contents of this folder to only authenticated users. Consequently, if an anonymous user visits this page they will be
automatically redirected back to the login page. As you can see in the screen shot below, user Randy is currently signed in.
There are two additional Web controls in the
ChangeUsername.aspx page that are not shown in the above screen shot. The first is a Label Web control named
lblErrors and is used to display information about any errors that occur during the username renaming process, such as
if the username cannot be changed because the desired new username is already used by someone else. There is also a RequiredFieldValidator control on the TextBox to ensure
that the user enters a new username.
Ensuring the New Username is Not Already Taken
When a user enters a username and clicks the "Change My Username" Button we need to make sure that the desired username is not already in use. This is accomplished by calling the
Membership.GetUsermethod and passing in the desired username. The
GetUsermethod will return a
MembershipUserobject if the username is already taken, and
null, in C#) otherwise. This logic is implemented in the
Note that if the username is already taken then the
Text property is set to an appropriate error string and control exits from
the event handler.
Changing the Username
As aforementioned, changing the username requires that we work directly with the underlying database to update the
LoweredUserNamefields in the
aspnet_Userstable. I created a stored procedure named
usp_ChangeUsernamethat performs this logic. The
usp_ChangeUsernamestored procedure accepts three input parameters:
@ApplicationName- the name of the application. This information is available programmatically via the
@OldUserName- the user's current username
@NewUserName- the user's desired new username
ApplicationIdvalues for the user from the
aspnet_Userstables. If no user is found in the database then the stored procedure exits with a return code of 1. Next, a final check is done to ensure that the username is not already taken. If it is taken then the stored procedure exits with a return code of 2. If everything checks out, the username is updated and the stored procedure returns a value of 0.
This stored procedure is called from a method named
ChangeUsername in a helper class named
UserAPI.vb, which you'll find in the
App_Code folder of the download at the end of this article. The
ChangeUsername method uses ADO.NET to connect to the database and execute the
stored procedure, passing in values for the three parameters and creating a parameter for the return value. The method returns True if the username was successfully changed,
This method is called from the
ChangeUsername.aspx web page with the following code:
User.Identity.Name returns the username of the currently logged in user.
newUsername is a variable that was declared earlier in the
handler and holds the value the user entered into the
Re-Signing the User In With His New Username
If the username is successfully updated we have one final step: we need to "re-sign in" the user. When a user signs in to a website a forms authentication ticket is created on their browser. This ticket is a token that identifies the user and includes their username, among other information. We need to update this authentication ticket to store the new username, otherwise the user will continue to see messages like, "Welcome back, OldUserName" until they sign out and re-sign in manually.
The good news is that it's remarkably easy to create the authentication ticket and specify the username. The .NET Framework includes a
which has a method named
method accepts two input parameters: the user's name and whether to stored the authentication in a persistent cookie.
(A persistent cookie is one that survives browser restarts, whereas a non-persistent one is trashed whenever the user closes their browser.)
The following code re-signs in the user by creating a new non-persistent authentication ticket with the new username. The user is then redirected to the site's homepage,
Default.aspx. Keep in mind that this code only runs if the call to
UserAPI.ChangeUsername returned True. If the
returned False then there was some problem in changing the user's name and a message is displayed indicating such.
The screen shot below shows the homepage after Randy has changed his username from Randy to Tito. Note that the "Welcome back" message shows the user's new username, Tito.
The technique for changing a username described in this article is sufficient for scenarios where changing the username requires only a change to the
LoweredUserNamefields in the
aspnet_Userstable. However, more database changes may be needed. In the Introduction I noted that the ability for users to change their username is paramount for sites that use an e-mail address as the username. If you are using an e-mail address as the username chances are you may be storing the e-mail address the user enters when creating an account in two places: in the
aspnet_Users.UserNamefield (and, by extension, in
LoweredUserName) and in the
aspnet_Membership.Emailfield. In that case, updating a user's username would entail also updating the
Many web applications that support user accounts have database tables that need to "point back" to a user record. This is commonly done by having a field in the related table
UserId that serves as a foreign key constraint back to
aspnet_Users.UserId. For example, consider an online messageboard site. This would have
the need to support user accounts and could use the Membership system. It would also have a table named
Posts that would have a record for each posting to the
messageboard. In order to determine which users made which posts we'd need to add a column to the
Posts table that links a post to a user. Ideally, this relationship
would be reflected by adding a
Posts.UserId field of type
uniqueidentifier and creating a foreign key constraint between that column (the foreign key)
aspnet_Users.UserId, the primary key.
However, I have seen plenty of database schemas where the database designer linked back on the
aspnet_Users.UserName field instead of
to the Posts example, that would mean that there would not be a
Posts.UserId field, but instead a
Posts.UserName field. If you used this approach
then allowing the user to change their username adds a bit of extra work because now when a user changes his username you also need to update each record in the
table, replacing the old
UserName value with the new one.
Both of these tasks - updating
aspnet_Membership.Email and updating
UserName columns in related tables - can be done by adding one or more
UPDATE statements to the
usp_ChangeUsername stored procedure.