For many years now, we’ve been writing code to implement forms authentication in our Internet applications. We’ve written the code to accept the user’s name and password, the code to hash and verify passwords, and the code to create and manage users. If you compare any two implementations of these features, you’ll probably find the architecture and code are similar. Starting with ASP.NET 2.0, web developers will no longer need to write and re-write the code to store and validate credentials. Instead, ASP.NET 2.0 provides membership and role providers as secure and extensible implementations for managing roles and membership in our web applications.
Authentication and Authorization
At their core, the membership and role providers exist to provide authentication and authorization services to our applications. Authentication is the process verifying the identity of a user. The membership provider can create new users and passwords in a database, and validate a user’s identity using the saved information. The membership provider uses a Microsoft SQL Server database. There is also a membership provider available for Active Directory, but this article will concentrate on the SQL Server membership provider.
ASP.NET 2.0 provides login controls we can drop on web forms to perform authentication with no code required. The controls talk directly to the membership provider. ASP.NET 2.0 also offers controls to support the ongoing maintenance of users, including changing passwords and resetting passwords. All of these controls build on top of features of the membership providers.
Once we know who a user is, we can find out what we will allow the user to do – this is authorization. The role providers in 2.0 allow us to create roles, and map users into the roles. For example, you might build an application with two roles: Administrators and RegisteredUsers. Given a username, the role manager can tell us to which roles a user belongs. Areas of a web application, or specific operations, can be restricted to exact roles.
Of course, your application might have special needs. Perhaps your database is not Microsoft SQL Server. Fortunately, Microsoft implemented both membership and role management using an extensible provider model. The provider model is the keystone of the membership and role services, so we will begin our tour of the functionality by covering what a provider does.
Providers
The provider model in ASP.NET 2.0 provides extensibility points for developers to plug their own implementation of a feature into the runtime. Both the membership and role features in ASP.NET 2.0 follow the provider pattern by specifying an interface, or contract. If you build your component to fulfill the contract a provider defines, you can plug your code into the ASP.NET runtime and replace or extend the existing providers. The provider model in ASP.NET 2.0 includes an infrastructure for the configuration and initialization of providers.
The provider model begins with the abstract class ProviderBase. ProviderBase exists to enforce the contract that all providers need public Name and Description properties, as well as a public Initialize method. Inheriting from ProviderBase are the MembershipProvider and RoleProvider abstract classes. These classes add additional properties and methods to define the interface for their specific areas of functionality.
As an example, the MembershipProvider requires a membership class to implement a ValidateUser method. The default membership provider in 2.0, the SqlMembershipProvider, implements this method by executing a stored procedure in a SQL Server database. If you want to write your own provider to use an XML file as a data store for membership information, you’ll have to write the code for ValidateUser to verify a user’s password against information kept in the XML file.
The beauty of the provider model is this: higher-level application services can build upon a provider and not need to know the details of what happens behind the interface. A good example is the ASP.NET 2.0 membership controls, which include a Login control, a CreateUser control, a LoginStatus control, and more. All of these controls program against the MembershipProvider contract. At some point, the login control will need to invoke the ValidateUser method on the configured provider. The login control doesn’t care if the call travels to a SQL Server database or an XML file. All the login control cares about is passing in a username and a password and receiving a true or false value in return.
The MembershipProvider
The purpose of the MembershipProvider is to provide a layer of indirection between membership controls, like the LoginControl, and the data store containing membership information. The indirection means we can use any data store (SQL Server, Oracle, XML, Web Service, Active Directory), as long as we have a provider to hide the details behind the public methods and properties of a concrete class. As we mentioned earlier, ASP.NET 2.0 includes providers for SQL Server and Active Directory.
A successful .NET installation will configure the SqlMembershipProvider class from the System.Web assembly as the default membership provider. You can verify the default by looking to the machine.config file, which applies settings to all the managed applications on a computer. The machine.config file is found in the config directory where the framework is installed, typically \Windows\Microsoft.NET\Framework\v2.0.xxxx.
<membership>
<providers>
<add
name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, ..."
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
/>
</providers>
</membership>
Configuring The Membership Provider
The membership section of machine.config contains a variety of knobs we can tweak – many of these tweaks involve user password management.
The passwordFormat property specifies how the provider will store passwords, and will impact a number of other membership features. The SqlMembershipProvider supports three formats: Hashed (the default and most secure format), Encrypted, and Clear. The hashed format passes a user’s plaintext password and a random salt value through a one-way hash algorithm before storing the password. You cannot retrieve a hashed password. To validate a password, the provider has to salt and hash the entered password and compare the two hash values (for more information on hashing passwords, see Pass The Salt). The provider can also store encrypted passwords (which can be decrypted and retrieved), or store passwords in the clear (which is not recommended).
The enablePasswordRetrieval option determines if the provider will return a user’s password with the GetPassword method. If the password format is set to Hashed, passwords are not retrievable. If the provider keeps passwords in an encrypted or clear text format, you could email a user’s forgotten password to them, but think of the security implications first. A more secure option in the event of a lost password is to reset the user’s password to a new value and email the new password (make sure to enforce unique email addresses with requiresUniqueEmail).
The enablePasswordReset property controls the ResetPassword API. ResetPassword will assign a new, generated password to a user. The PasswordRecovery control can automatically email the new password to a user. It a good idea to set the requiresQuestionAndAnswer property to true to prevent a malicious user from resetting someone else’s password, A value of true means the user has to provide the answer to a security question before resetting their password. The question and answer text is will be required by the CreateUser control when a adding a new user.
A number of properties control the password strength a provider will allow. The minRequiredPasswordLength and minRequiredNonalphanumericCharacters prevent users from choosing a password like “abc”. If you have additional requirements, you can use the passwordStrengthRegularExpression property to force the password to pass a regular expression test. Note: a password generated by ResetPassword will always meet the required password length and required number of non-alphanumeric characters, but may not meet pass the regular expression test.
The SqlMembershipProvider offers a number of features not shown in the configuration above. For instance, the maxInvalidPasswordAttempts and passwordAttemptWindow properties work together to prevent a malicious user from using brute force techniques to break into a user account. Too many bad passwords will lock out a user account and prevent the account from logging in until the account is unlocked with the UnlockUser method.
Membership and SQL Server
Other properties in the membership section control how SqlMembershipProvider interacts with SQL Server. By default, the machine.config file configures membership and roles to work with a SQL Server Express database file in the App_Data directory. Looking back at the configuration excerpt above, we see the connectionStringName property is “LocalSqlServer”. If you locate the connectionStrings section of machine.config you’ll find the following:
<add name="LocalSqlServer"
connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
providerName="System.Data.SqlClient" />
You can always override the default setting and point all providers using LocalSqlServer to a remote database, or a non-Express database on the local machine. The first step would be to use the ASP.NET Sql Server Registration Tool (aspnet_regsql.exe) to create a new database. You can find the tool in the .NET framework installation directory (WINDOWS\Microsoft.NET\Framework\2.0.xxxx). If you launch the tool without command line parameters, the tool will launch a wizard to walk through the setup for a new database. The default database name is aspnetdb.
One you’ve configured a database for the provider to use, you can modify the web.config file for your application to redefine the LocalSqlServer connection string to point to the new database.
<connectionStrings>
<remove name="LocalSqlServer"/>
<add name="LocalSqlServer"
connectionString="server=.;database=aspnetdb;integrated security=sspi;"/>
</connectionStrings>
Alternatively, you can define a new connection string and modify the connectionStringName property of a provider to use the new connection string.
You can test your settings with the ASP.NET Configuration tool (under the Website menu is Visual Studio). On the Provider tab, choose “Select a different provider for each feature”, and you’ll arrive at the following page that allows you to “test” each provider’s connectivity. The administration tool also contains pages to manage security settings, create users, and more.
Another important property to set in the membership configuration is the applicationName property. The applicationName allows one database to support multiple web applications. If you have two web applications and want both apps to share the same user base, give both applications the same applicationName and point them to the same aspnetdb database. If you want both applications to use the same database but not share users, give each application a unique applicationName property.
Using The Membership Provider
If you want to interact directly with the Membership API, one approach is to use the Membership class from System.Web.Security. The Membership class contains only static members and properties, but these static members map to properties and methods on the MembershipProvider, and the component will forward calls to the configured provider when appropriate. Here is an example using hard coded values for a user's attributes.
string username = "SwedishChef";
string password = "bj#kbj1k";
string email = @"swede@mailinator.com";
string question = "The greatest band ever?";
string answer = "ABBA";
bool isApproved = true;
MembershipCreateStatus status;
Membership.CreateUser(
username, password, email,
question, answer, isApproved,
out status);
if(status == MembershipCreateStatus.Success)
{
// party!
}
An even easier interface to the membership provider is to use the ASP.NET 2.0 Login controls: Login, LoginView, PasswordRecovery, LoginStatus, LoginName, CreateUserWizard, and ChangePassword. The Login control, for example, will ultimately call the ValidateUser method of the current membership provider when a user enter their username and password and clicks the Login button. There is no need to write any code if the built-in controls provide all the functionality you need. All of the controls allow customization various levels of customization through styles and templates. You can find the controls in the Visual Studio toolbox under the “Login” category.
Up Next
In Part II of this article, we will take a look at role providers in ASP.NET.
Questions? Feedback? Bring them here.