Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Developing custom ASP.NET Membership and Role providers reading users from custom section in the web.config

0.00/5 (No votes)
7 Aug 2008 1  
In this article, we will be developing custom Membership and Role providers which will read user credentials from a custom configuration section in the web.config file.

Introduction

Recently, I developed a small web application in order to be able to do some basic file-management in my intranet web server. Having finished my app, I thought I should add some sort of security in order to prevent the other network users from tampering with my files. Thus, ASP.NET security came to mind. The only problem was that I wanted to avoid the use of a database, so I couldn’t use the default SQL membership and role providers. There are hundreds of articles on how to create custom membership and role providers, but all of them require a database to read the data from. I wanted something simple and quite secure, so I thought I should save my users’ and roles’ data in the most secure file in ASP.NET, which is no other than the web.config file.

In the first part, we will be developing a custom configuration section in order to store the roles and users data in the web.config file. In the second part, we will be briefly implementing the role and membership providers, and then, in the final part, we will be creating a small demo web application to see the security model in action. By the end of this article, you will be able to use all the out-of-the-box ASP.NET login controls and even the ASP.NET Web Site Administration Tool in order to configure your access rules.

Part 1: Custom Configuration Section

I first came upon custom configuration sections when I started working with the provider model, and to be honest, I used to configure the whole thing mechanically rather than actually understanding what exactly was going on under the hood. This is where Jon Rista’s series of articles entitled “Unraveling the Mysteries of .NET 2.0 Configuration” comes into play. I suggest reading at least the first part if you get lost in here.

In order to record our usernames and passwords, we will be adding a section in the web.config that looks like this:

<configuration>
  ...
  <!-- This is the new section we have configured-->
  <CustomUsersSection>
    <!-- Which has 2 property the Roles-->
    <Roles>
      <!-- Roles is a collection so it has two entries-->
      <add RoleName="User"/>
      <add RoleName="Administrator"/>
    </Roles>
    <!-- and the users-->
    <Users>
      <!-- You may add here as many users as you like -->
      <add UserName="auser" Password="password" 
           Email="abot@home" Role="User"/>
      <add UserName="admin" Password="password" 
           Email="abot@home" Role="Administrator"/>
    </Users>
  </CustomUsersSection>
  ...
</configuration>

Looking at the above mentioned XML and thinking a little bit in OOP terms, we could say that it describes a class named CustomUsersSection which has two properties: Roles and Users. Roles and Users are collections of items. If we now pay a little bit more attention on the add tag and observe the attributes, we could say that the following XML:

<add rolename="User" />

represents an instance of a class with the property RoleName set to the value “User”, while the following XML:

<add username="auser" password="password" email="abot@home" role="User" />

represents another instance of another class that has the following properties and values:

  • UserName - auser
  • Password - password
  • Email - abot@home
  • Role - User

Well, this is exactly what this is all about: a hierarchy of classes. There are five classes involved in the above mentioned XML: one that represents the section (UsersConfigurationSection), another to represent the collection of roles (CustomRolesCollection), a class to represent the roles (CustomRole), a class to represent the collection of users (CustomUsersCollection), and finally, the class that represents the user (CustomUser).

We shall start with the self explainable classes, CustomRole and CustomRolesCollection, located in the App_Code>Configuration> CustomRole.vb file:

Namespace Configuration
    Public Class CustomRole
        Inherits ConfigurationElement

        <ConfigurationProperty("RoleName", IsRequired:=True)> _
        Public ReadOnly Property RoleName() As String
            Get
                Return MyBase.Item("RoleName")
            End Get
        End Property

    End Class

    Public Class CustomRolesCollection
        Inherits ConfigurationElementCollection

        Protected Overloads Overrides Function CreateNewElement() _
                  As System.Configuration.ConfigurationElement
            Return New CustomRole
        End Function

        Protected Overrides Function GetElementKey(ByVal element _
                  As System.Configuration.ConfigurationElement) As Object
            Return CType(element, CustomRole).RoleName
        End Function

        'It is generally necessary to provide 
        'an indexer accessible by numeric index. 
        Public Shadows ReadOnly Property Item(ByVal index _
                       As Integer) As CustomRole
            Get
                Return MyBase.BaseGet(index)
            End Get
        End Property

        'An indexer accessible by an element's key 
        'is also a useful convenience
        Public Shadows ReadOnly Property Item(ByVal rolename _
                       As String) As CustomRole
            Get
                Return MyBase.BaseGet(rolename)
            End Get
        End Property
    End Class
End Namespace

These two inherit from the ConfigurationElement and the ConfigurationElementCollection classes, respectively. The ConfigurationElement is a single entity which has properties, while the ConfigurationElementCollection is a collection of ConfigurationElements. In our case, the CustomRole class has a ReadOnly property named RoleName, which actually recalls the base’s attribute RoleName. Thus, the framework reads the XML attribute and is inherited into the property. When you create properties, be sure to add the following compiler declaration:

<ConfigurationProperty("propertyname", IsRequired:=True)> _

On the other hand, the CustomRolesCollection, which inherits from ConfigurationElementCollection, is only required to implement the CreateNewElement and the GetElementKey functions. The two item property implementations help to retrieve the items from the collection since, by default, the item property is private.

On the same idea, I cite the CustomUser and the CustomUsersCollection from the App_Code>Configuration>CustomUser.vb file:

Imports System.Web.Security
Namespace Configuration
    Public Class CustomUser
        Inherits ConfigurationElement

        <ConfigurationProperty("UserName", IsRequired:=True)> _
        Public ReadOnly Property UserName() As String
            Get
                Return MyBase.Item("UserName")
            End Get
        End Property

        <ConfigurationProperty("Password", IsRequired:=True)> _
        Public ReadOnly Property Password() As String
            Get
                Return MyBase.Item("Password")
            End Get
        End Property

        <ConfigurationProperty("Role", IsRequired:=True)> _
        Public ReadOnly Property Role() As String
            Get
                Return MyBase.Item("Role")
            End Get
        End Property

        <ConfigurationProperty("Email", IsRequired:=True)> _
        Public ReadOnly Property Email() As String
            Get
                Return MyBase.Item("Email")
            End Get
        End Property

        Public ReadOnly Property AspNetMembership(ByVal ProviderName _
                        As String) As System.Web.Security.MembershipUser
            Get
                Return New MembershipUser(ProviderName, Me.UserName, _
                       Me.UserName, Me.Email, "", "", True, False, _
                       Date.Now, Date.Now, Date.Now, Date.Now, Nothing)
            End Get
        End Property
    End Class

    Public Class CustomUsersCollection
        Inherits ConfigurationElementCollection

        Protected Overloads Overrides Function CreateNewElement() _
                  As System.Configuration.ConfigurationElement
            Return New CustomUser
        End Function

        Protected Overrides Function GetElementKey(ByVal element As _
                  System.Configuration.ConfigurationElement) As Object
            Return CType(element, CustomUser).UserName
        End Function

        'It is generally necessary to provide 
        'an indexer accessible by numeric index. 
        Public Shadows ReadOnly Property Item(ByVal _
                       index As Integer) As CustomUser
            Get
                Return MyBase.BaseGet(index)
            End Get
        End Property

        'An indexer accessible by an element's key 
        'is also a useful convenience
        Public Shadows ReadOnly Property Item(ByVal username _
                                As String) As CustomUser
            Get
                Return MyBase.BaseGet(username)
            End Get
        End Property
    End Class
End Namespace

The only addition to the above mentioned philosophy is the AspNetMembership readonly property that I added in the CustomUser, which returns a dummy System.Web.Security.MembershipUser needed by the Membership provider. In order to add another property to CustomUser, you simply add the following code in the class:

<ConfigurationProperty("UserName", IsRequired:=True)> _
Public ReadOnly Property AnotherProperty () As String
    Get
       Return MyBase.Item("AnotherProperty ")
    End Get
End Property

And, don’t forget to complete the attribute in the web.config file.

The final class is located in the App_Code>Configuration>UsersConfigurationSection.vb file, and is the one that handles the whole configuration section we created, and thus, inherits from ConfigurationSection:

Imports System.Configuration
Namespace Configuration
    Public Class UsersConfigurationSection
        Inherits ConfigurationSection

        <ConfigurationProperty("Roles")> _
        Public ReadOnly Property Roles() As CustomRolesCollection
            Get
                Return MyBase.Item("Roles")
            End Get
        End Property

        <ConfigurationProperty("Users")> _
        Public ReadOnly Property Users() As CustomUsersCollection
            Get
                Return MyBase.Item("Users")
            End Get
        End Property

        Public Shared ReadOnly Property Current() _
                      As UsersConfigurationSection
            Get
                Return ConfigurationManager.GetSection("CustomUsersSection")
            End Get
        End Property

    End Class
End Namespace

As you may have noticed, the Roles and the Users properties are configuration properties of the section. I have also added a shared read only property called current in order to get the instance named “CustomUsersSection” that is saved in the web.config file, using the ConfigurationManager class.

So, by now, we have the whole class hierarchy, and we need to instruct the .NET framework that there is a new section in the web.config file that is of type UsersConfigurationSection and that it will be named “CustomUsersSection”. If we look exactly below the configuration tag of the provided web.config file, we will see the following directives:

<ConfigSections >
    <section name="CustomUsersSection" 
        type="Configuration.UsersConfigurationSection, App_Code" />
</ConfigSections>

This is exactly what we want. Don’t be alarmed by the Configuration namespace, I simply wanted to pack the classes under a namespace.

The final web.config file would look like this:

<configuration>
    <ConfigSections>
        <section name="CustomUsersSection" 
               type="Configuration.UsersConfigurationSection, App_Code" />
    </ConfigSections>
    <CustomUsersSection>
        <roles>
            <add rolename="User" />
            <add rolename="Administrator" />
        </roles>
        <users>
            <add role="User" email="abot@home" 
                   password="password" username="auser" />
            <add role="Administrator" email="abot@home" 
                   password="password" username="admin" />
        </users>
    </CustomUsersSection>
    ...
</configuration />

Part 2: Custom Membership and Role Providers

If you search the net with the following three keywords “Custom Membership Provider”, you will end up with millions of pages and blogs describing how to build your custom Membership provider. This is mainly done by inheriting from the abstract RoleProvider and MembershipProvider classes and then overriding the required functions. In our case, we will not provide the capability to modify the users and the roles, so we will be throwing a lot of NotSupportedException exceptions :-). The main difference between any other custom membership provider and this implementation is that the data is derived from the read only properties UsersConfigurationSection.Current.Users and UsersConfigurationSection.Current.Roles. Thus, the WebConfigMembershipProvider class (App_Code>AspNetProvider>WebConfigMembershipProvider.vb) should look something like this (a lot of optimization is welcomed here):

Imports Configuration
Namespace AspNetProvider
    Public Class WebConfigMembershipProvider
        Inherits MembershipProvider

        Private _ApplicationName As String

        Public Overrides Sub Initialize(ByVal name As String, _
               ByVal config As System.Collections.Specialized.NameValueCollection)
            '===retrives the attribute values set in
            'web.config and assign to local variables===
            _ApplicationName = config("ApplicationName")
            MyBase.Initialize(name, config)
        End Sub

        Public Overrides Property ApplicationName() As String
            Get
                Return _ApplicationName
            End Get
            Set(ByVal value As String)
                _ApplicationName = value
            End Set
        End Property

        Public Overrides Function ChangePassword(ByVal username As String, _
               ByVal oldPassword As String, ByVal newPassword As String) As Boolean
            Throw New System.NotSupportedException("No saving ")
        End Function

        Public Overrides Function ChangePasswordQuestionAndAnswer(ByVal username As String, _
               ByVal password As String, ByVal newPasswordQuestion As String, _
               ByVal newPasswordAnswer As String) As Boolean
            Throw New System.NotSupportedException("Not implemented")
        End Function

        Public Overrides Function CreateUser(ByVal username As String, _
               ByVal password As String, ByVal email As String, _
               ByVal passwordQuestion As String, ByVal passwordAnswer As String, _
               ByVal isApproved As Boolean, ByVal providerUserKey As Object, _
               ByRef status As System.Web.Security.MembershipCreateStatus) _
               As System.Web.Security.MembershipUser
            Throw New System.NotSupportedException("Not implemented")
        End Function

        Public Overrides Function DeleteUser(ByVal username As String, _
               ByVal deleteAllRelatedData As Boolean) As Boolean
            Throw New System.NotSupportedException("Not implemented")
        End Function

        Public Overrides ReadOnly Property EnablePasswordReset() As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property EnablePasswordRetrieval() As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides Function FindUsersByEmail(ByVal emailToMatch As String, _
               ByVal pageIndex As Integer, ByVal pageSize As Integer,  _
               ByRef totalRecords As Integer) As System.Web.Security.MembershipUserCollection
            Dim output As New System.Web.Security.MembershipUserCollection
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.Email.Equals(emailToMatch) Then
                    output.Add(user.AspNetMembership(Me.Name))
                End If
            Next
            Return output
        End Function

        Public Overrides Function FindUsersByName(ByVal usernameToMatch As String, _
               ByVal pageIndex As Integer, ByVal pageSize As Integer,  _
               ByRef totalRecords As Integer) As System.Web.Security.MembershipUserCollection
            Dim output As New System.Web.Security.MembershipUserCollection
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.UserName.Equals(usernameToMatch) Then
                    output.Add(user.AspNetMembership(Me.Name))
                End If
            Next
            Return output
        End Function

        Public Overrides Function GetAllUsers(ByVal pageIndex As Integer, _
               ByVal pageSize As Integer, ByRef totalRecords As Integer)  _
               As System.Web.Security.MembershipUserCollection
            Dim output As New System.Web.Security.MembershipUserCollection
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                output.Add(user.AspNetMembership(Me.Name))
            Next
            Return output
        End Function

        Public Overrides Function GetNumberOfUsersOnline() As Integer
            Throw New System.NotSupportedException("No saving ")
        End Function

        Public Overrides Function GetPassword(ByVal username As String, _
               ByVal answer As String) As String
            Throw New System.NotSupportedException("The question/answer" & _ 
                             " model is not supported yet!")
        End Function

        Public Overloads Overrides Function GetUser(ByVal providerUserKey As Object, _
               ByVal userIsOnline As Boolean) As System.Web.Security.MembershipUser
            Dim output As System.Web.Security.MembershipUser = Nothing
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.UserName.Equals(providerUserKey) Then
                    output = user.AspNetMembership(Me.Name)
                End If
            Next
            Return output
        End Function

        Public Overloads Overrides Function GetUser(ByVal username As String, _
               ByVal userIsOnline As Boolean) As System.Web.Security.MembershipUser
            Dim output As System.Web.Security.MembershipUser = Nothing
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.UserName.Equals(username) Then
                    output = user.AspNetMembership(Me.Name)
                End If
            Next
            Return output
        End Function

        Public Overrides Function GetUserNameByEmail(ByVal email As String) As String
            Dim output As String = Nothing
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.Email.Equals(email) Then
                    output = user.UserName
                End If
            Next
            Return output
        End Function

        Public Overrides ReadOnly Property MaxInvalidPasswordAttempts() As Integer
            Get
                Return 5
            End Get
        End Property

        Public Overrides ReadOnly Property _
               MinRequiredNonAlphanumericCharacters() As Integer
            Get
                Return 0
            End Get
        End Property

        Public Overrides ReadOnly Property MinRequiredPasswordLength() As Integer
            Get
                Return 8
            End Get
        End Property

        Public Overrides ReadOnly Property PasswordAttemptWindow() As Integer
            Get
                Return 30
            End Get
        End Property

        Public Overrides ReadOnly Property PasswordFormat() _
               As System.Web.Security.MembershipPasswordFormat
            Get
                Return MembershipPasswordFormat.Clear
            End Get
        End Property

        Public Overrides ReadOnly Property _
               PasswordStrengthRegularExpression() As String
            Get
                Return ""
            End Get
        End Property

        Public Overrides ReadOnly Property RequiresQuestionAndAnswer() As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides ReadOnly Property RequiresUniqueEmail() As Boolean
            Get
                Return False
            End Get
        End Property

        Public Overrides Function ResetPassword(ByVal username As String, _
                         ByVal answer As String) As String
            Throw New System.NotSupportedException("No saving")
        End Function

        Public Overrides Function UnlockUser(ByVal userName As String) As Boolean
            Throw New System.NotSupportedException("No saving")
        End Function

        Public Overrides Sub UpdateUser(ByVal user As System.Web.Security.MembershipUser)
            Throw New System.NotSupportedException("No saving")
        End Sub

        Public Overrides Function ValidateUser(ByVal username As String, _
                         ByVal password As String) As Boolean
            Dim output As Boolean = False
            If username.Length > 0 Then
                Dim myUser As CustomUser = _
                           UsersConfigurationSection.Current.Users.Item(username)
                If myUser IsNot Nothing Then
                    output = myUser.Password.Equals(password)
                End If
            End If
            Return output
        End Function
    End Class
End Namespace

The WebConfigRoleProvider class (App_Code>AspNetProvider>WebConfigRoleProvider.vb) is as follows:

Imports Configuration

Namespace AspNetProvider

    Public Class WebConfigRoleProvider
        Inherits RoleProvider

        Private _ApplicationName As String

        Public Overrides Sub Initialize(ByVal name As String, ByVal config _
                         As System.Collections.Specialized.NameValueCollection)
            '===retrives the attribute values set in           
            'web.config and assign to local variables===
            _ApplicationName = config("ApplicationName")
            MyBase.Initialize(name, config)
        End Sub

        Public Overrides Sub AddUsersToRoles(ByVal usernames() As String, _
                                             ByVal roleNames() As String)
            Throw New System.NotSupportedException("No saving")
        End Sub

        Public Overrides Property ApplicationName() As String
            Get
                Return _ApplicationName
            End Get
            Set(ByVal value As String)
                _ApplicationName = value
            End Set
        End Property

        Public Overrides Sub CreateRole(ByVal roleName As String)
            Throw New System.NotSupportedException("No saving")
        End Sub

        Public Overrides Function DeleteRole(ByVal roleName As String, _
               ByVal throwOnPopulatedRole As Boolean) As Boolean
            Throw New System.NotSupportedException("No saving")
        End Function

        Public Overrides Function FindUsersInRole(ByVal roleName As String, _
               ByVal usernameToMatch As String) As String()
            Dim output As New ArrayList
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.Role.Equals(roleName) AndAlso _
                        user.UserName.Equals(usernameToMatch) Then
                    output.Add(user.UserName)
                End If
            Next
            Return output.ToArray(GetType(String))
        End Function

        Public Overrides Function GetAllRoles() As String()
            Dim myRoles As New ArrayList
            For Each role As CustomRole In UsersConfigurationSection.Current.Roles
                myRoles.Add(role.RoleName)
            Next
            Return myRoles.ToArray(GetType(String))
        End Function

        Public Overrides Function GetRolesForUser(ByVal username As String) As String()
            Dim user As CustomUser = _
                     UsersConfigurationSection.Current.Users.Item(username)
            If user IsNot Nothing Then
                'Only one role per user is currently supported
                Return New String() {user.Role}
            Else
                  Return New String() {}
            End If
        End Function

        Public Overrides Function GetUsersInRole(ByVal roleName As String) As String()
            Dim output As New ArrayList
            For Each user As CustomUser In UsersConfigurationSection.Current.Users
                If user.Role.Equals(roleName) Then
                    output.Add(user.UserName)
                End If
            Next
            Return output.ToArray(GetType(String))
        End Function

        Public Overrides Function IsUserInRole(ByVal username As String, _
                         ByVal roleName As String) As Boolean
            Dim user As CustomUser = UsersConfigurationSection.Current.Users.Item(username)
            If user IsNot Nothing Then
                Return user.Role.Equals(roleName)
            Else
                Return False
            End If
        End Function

        Public Overrides Sub RemoveUsersFromRoles(ByVal usernames() As String, _
                         ByVal roleNames() As String)
            Throw New System.NotSupportedException("No saving")
        End Sub

        Public Overrides Function RoleExists(ByVal roleName As String) As Boolean
            Return UsersConfigurationSection.Current.Roles.Item(roleName) IsNot Nothing
        End Function
    End Class

End Namespace

Hopefully, the functions are self-explainable since they do have ridiculously over-descriptive names. For completeness’ sake, I will cite the configuration required in the web.config in order to use the custom providers. The whole configuration is performed in the configuration>system.web section, and looks like this:

       <!--
            The <authentication> section enables configuration 
            of the security authentication mode used by 
            ASP.NET to identify an incoming user. 
            In our case the authentication will be handled by a login form-->
      <authentication mode="Forms"/>
      <!-- We define the rolemanager that will be handling the website's roles requests-->
      <roleManager enabled="true" defaultProvider="WebConfigRoleProvider">
        <providers>
          <!-- Clear the providers inherited by either 
               the machine.config or any parent application-->
          <clear/>
          <!-- Add our custom role provider-->
          <add name="WebConfigRoleProvider" 
            type="AspNetProvider.WebConfigRoleProvider" applicationName="WebSite"  _ 
            enabled="true" cacheRolesInCookie="true" cookieName=".ASPROLES" 
            cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" _ 
            cookieSlidingExpiration="true" cookieProtection="All" />
        </providers>
      </roleManager>
      <!-- And here we define the custom membership provider-->
      <membership defaultProvider="WebConfigMembershipProvider">
        <providers>
          <clear/>
          <add name="WebConfigMembershipProvider" 
               type="AspNetProvider.WebConfigMembershipProvider" 
               applicationName="Website"/>
        </providers>
      </membership>

Part 3: Demo Web Application

As a proof of concept, I have built a website which doesn’t allow anonymous users (see the authorization section in the web.config file). Thus, when the user visits the website, he is redirected to the login form.

Both the login.aspx and the default.aspx pages utilize the controls from the Login tab in the toolbox, as seen in the following screen:

Common login controls

The most interesting lines of code are:

  • My.User.Name: Gets the username
  • My.User.IsInRole("Administrator"): Checks if the logged-in user is an Administrator
  • Configuration.UsersConfigurationSection.Current.Users.Item(my.User.Name).Email: Gets the user’s email address

Finally, I should note that you can use the ASP.NET Web Site Administration Tool in order to configure the access rules of the website directories.

Asp.net Web Site Administration Tool

Conclusions

In this article, we have seen an innovative approach to implementing membership and role providers. The user credentials are stored in the web.config file in a custom configuration section, and we were able to use the out-of-the-box login controls in order to validate the user.

Possible Enhancements

One major possible enhancement for the given code is to allow the update of users and roles. The method for saving configuration changes is discussed in this article.

Moreover, a possible security update would be to encrypt or even hash the passwords. I would personally prefer hashing the password since it’s much safer. To authenticate a user, the password presented by the user is hashed and compared with the stored hash. In this case, we would have to add a new class called Hashing:

Imports System.Security.Cryptography
Imports System.Text
Public Class Hashing
    Public Shared Function HashInMD5(ByVal cleanString As String) As String
        Dim clearBytes As [Byte]()
        clearBytes = New UnicodeEncoding().GetBytes(cleanString)
        Dim hashedBytes As [Byte]() = _
            CType(CryptoConfig.CreateFromName("MD5"), _
            HashAlgorithm).ComputeHash(clearBytes)
        Dim hashedText As String = BitConverter.ToString(hashedBytes)
        Return hashedText
    End Function

End Class

And then, in the WebConfigMembershipProvider.vb file, we would have to change the ValidateUser function to the following:

Public Overrides Function ValidateUser(ByVal username As String, _
                 ByVal password As String) As Boolean
    Dim output As Boolean = False
    If username.Length > 0 Then
        Dim myUser As CustomUser = _
            UsersConfigurationSection.Current.Users.Item(username)
        If myUser IsNot Nothing Then
            output = myUser.Password.Equals(Hashing.HashInMD5(password))
        End If
    End If
    Return output
End Function

Thanks to a discussion with about:blank / colin in the forum, I was prompted to modify the code to support multiple roles per user. In order to locate the changes, find the '=========CHANGE HERE=============' comments.

Feedback and Voting

If you have read this far, please remember to vote. If you do or don't like, agree, or disagree with what you have read in this article, then please say so in the forum below. Your feedback is imperative since this is my first CodeProject article!

History

  • 21 July 2008: First version.
  • 7 August 2008: Added multiple roles per user support under the possible enhancements section.

P.S.: If you do not want to implement a role provider, then you should revert to this article which uses the built-in .NET authentication model to store usernames and passwords, but it needs an extension in order to support roles.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here