Using the ASP.NET membership provider
in a Windows forms application
- Maurice de Beijer -
One of the new features in ASP.NET 2.0 is the membership and role provider
system. This is a nicely designed system that allows the developer to handle the
storage of user information pretty much any way he likes, all he has to do is
create the appropriate providers and configure his application to use these.
While this is very powerful there is something else I was much more interested
in and that is the fact that user and role management can be added to an
application without having to do any of the implementation work. Now this is
very convenient if you don’t already have a user database to work with. Also, if
at all possible, I would like to be able to use these providers in my Windows forms applications as
well as in my web application. Fortunately this is possible without much work at
all! So let’s create a very small console application with user management.
Registering and validating users
First create a new Visual Basic console application in Visual Studio 2005. No
problem if you prefer C#, all you need to do is translate the remaining syntax
to C# but I leave that up to you :-). Next add a reference to the System.Web
assembly in the project properties.
Add the following line to the top of the Module1.vb:
Imports
System.Web.Security
Now add the following code to the Sub Main()
' Validate a username/password
If Membership.ValidateUser("Maurice",
"Password_1") Then
Console.WriteLine("User
validated.")
Else
Console.WriteLine("User invalid!")
End If
Console.ReadKey()
Go ahead and run the application at this point. Not surprisingly the application
reports that the user is invalid, after all it can’t know which users are
valid.
Now add the following code to the top of the Sub Main(), before the
ValidateUser call:
' Creating a new user
Dim status As
MembershipCreateStatus
Membership.CreateUser( _
"Maurice", _
"Password_1", _
"maurice@TheProblemSolver.nl", _
"Password question", _
"Password answer", _
True, _
status)
Console.WriteLine(status.ToString())
Go ahead and run the application again at this point.
If you have never used the membership provider in a Web application you might
me surprised to see that this just works, the user is created and is validated. Run
the application again and the creation will return a DuplicateUserName error but
the ValidateUser() still works. How is this possible as we haven’t done anything
to configure the membership provider or told it which database to use? Well the
secret is that the membership provider checks for, and creates if needed, a new
directory under the application directory named App_Data with a SQL Express
database ASPNETDB with the required structure. This database is then used to
store the user information. To see the database click on the Show All Files
button in the explorer, Open the bin/Debug folder and you will see an App_Data
folder with the ASPNETDB.MDF database inside of it.
So far so good, I had to do very little work and I can
already register and validate users. However I had to supply quite a bit of
information, like password question and answer. Something I would not typically
use in a windows application. Fortunately the Membership.CreateUser() also has
an overload with just the username and password combination, more along the
lines of what I would like to use. Unfortunately adding the following code the
Sub Main doesn’t work but it raises a System.Web.Security.MembershipCreateUserException with the text “The
password-answer supplied is invalid.”.
Dim user As
MembershipUser
user = Membership.CreateUser("User2",
"Password")
Console.WriteLine(user.UserName)
Additionally there might be some other membership properties you might like to
change, like the requirement for at least one non alphanumeric character in the
password. Even though these settings are exposed as properties they are
read-only so setting them like:
Membership.MinRequiredNonAlphanumericCharacters = 0 will not work. To
change them we need to use the applications app.config. As we don’t have one yet
go ahead and add one to the application. Now add the following to the bottom of
the <configuration>
section:
<system.web>
<membership>
<providers>
<remove
name="AspNetSqlMembershipProvider"/>
<add
name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider,
System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="1"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
/>
</providers>
</membership>
</system.web>
This might seem a little strange at first, after all we are adding a
system.web configuration block to a console application. But remember that the
Membership provider was written by the ASP.NET team for ASP.NET use and the code
just looks in the configuration file for the current application. So what does
this actually do? This configuration block removes the default membership
provider as defined in the machine.config and adds the same one but this time
with more relaxed password format and questions settings. Now run the
application again and this time we are allowed to add a user with just a
username and password combination. Because of the relaxed password requirements
even a password of just 1 single letter would be accepted in this case.
Adding user roles
Now that we can add valid users we might want to start using roles as well.
To work with security roles we need to use the class System.Web.Security.Roles.
This class works pretty much in the same way as the Membership provider. To add
a new role we use the CreateRole() function and to associate a user with this
role we use the AddUserToRole() function. So adding the Developer role to my
previously created user account can be done with the following code:
Roles.CreateRole("Developer")
Roles.AddUserToRole("Maurice",
"Developer")
Unfortunately this isn't quite as simple as we receive a System.Configuration.Provider.ProviderException exception with the text “The
Role Manager feature has not been enabled.”. Fortunately the fix isn’t difficult,
all we need to do is add the following line to the
<system.web>
section in the App.Config:
<roleManager
enabled="true"
/>
Once this is done you are ready to add roles and associate users with these
roles. Now you can use the following code to check if a user has a specific
role:
If Roles.IsUserInRole("Maurice",
"Developer") Then
Console.WriteLine("Is a developer.")
Else
Console.WriteLine("Doesn't write code.")
End If
Remembering the current user
Of course this leaves us with a place to store the current user name so we
know which user is logged in when we need to check for a specific role. The
IsUserInRole() function has an additional overload that takes only a single
parameter, the role name. Taking a look at the internals of the IsUserInRole()
function shows that it determines the current user either via the
HttpContext.Current.User or the Threading.Thread.CurrentPrincipal depending on
the fact if the running application is hosted. As the
Threading.Thread.CurrentPrincipal is the normal way to provide the current user
and role information in a windows application this seems like the natural thing
to do. Again there are several standard classes available to do this so we need
to write almost no code. The Threading.Thread.CurrentPrincipal is defined as
interface IPrincipal so we need a type that implements that interface. The
ASP.NET team provides the System.Web.Security.RolePrincipal for just this
purpose. This type takes an parameter of type System.Security.Principal.IIdentity in its constructor. Again this is readily
available in the form of a System.Security.Principal.GenericIdentity type that
takes the users name as the parameter to its constructor. Using these types it
is easy to save the validated user.
Add the following code to the top of the Module1.vb:
Imports
System.Security.Principal
Now we can set the principal and check for specific roles using the following
code:
Dim user As
MembershipUser = Membership.GetUser("Maurice")
Dim identity As
New GenericIdentity(user.UserName)
Dim principal
As New RolePrincipal(identity)
Threading.Thread.CurrentPrincipal = principal
If Roles.IsUserInRole("Developer")
Then
Console.WriteLine("Is a developer.")
Else
Console.WriteLine("Doesn't write code.")
End If
An alternative way, but slightly more verbose way, of checking the user and
role is:
Console.Write(Threading.Thread.CurrentPrincipal.Identity.Name)
If
Threading.Thread.CurrentPrincipal.IsInRole("Developer")
Then
Console.WriteLine(" is a developer.")
Else
Console.WriteLine(" doesn't write code.")
End If
Conclusion
In this article I have tried to demonstrate how easy it is to use the ASP.NET Memebership and Role providers in a non web application. It turns out
that this is very simple to do and quite beneficial if you need that kind of
functionality. Quite frankly user authentication is something that I need in
most of my applications and role management is something that comes up quite
often too so I will certainly be using these providers in future applications.
Complete listing
Imports
System.Security.Principal
Imports
System.Web.Security
Module
Module1
Sub Main()
' Creating a new user
Dim status As
MembershipCreateStatus
Membership.CreateUser( _
"Maurice", _
"Password_1", _
"maurice@TheProblemSolver.nl", _
"Password question", _
"Password answer", _
True, _
status)
' Check the status for errors
Console.WriteLine(status.ToString())
' Validate a
username/password
If Membership.ValidateUser("Maurice",
"Password_1")
Then
Console.WriteLine("User
validated.")
Else
Console.WriteLine("User
invalid!")
End If
' Create a new Developer role.
' Add the <roleManager enabled="true" /> to
the app.config for this to work
If Not
Roles.RoleExists("Developer")
Then
Roles.CreateRole("Developer")
End If
' Add a new role to a known user.
If Not
Roles.IsUserInRole("Maurice",
"Developer") Then
Roles.AddUserToRole("Maurice",
"Developer")
End If
' Create a second user with only
username/password
' Add the <membership><providers> element to
the app.config first
Dim user As
MembershipUser
user = Membership.GetUser("User2")
If user Is
Nothing Then
user = Membership.CreateUser("User2",
"p")
Console.WriteLine(user.UserName)
End If
' Check is a specified user is in a
specific role
If Roles.IsUserInRole("Maurice",
"Developer") Then
Console.WriteLine("Is
a developer.")
Else
Console.WriteLine("Doesn't
write code.")
End If
' Set the current application principal information to
a known user
Dim identity As
GenericIdentity
Dim principal
As RolePrincipal
user = Membership.GetUser("Maurice")
identity = New
GenericIdentity(user.UserName)
principal = New RolePrincipal(identity)
Threading.Thread.CurrentPrincipal = principal
' Check if the current principal is in a
specific role
If Roles.IsUserInRole("Developer")
Then
Console.WriteLine("Is a developer.")
Else
Console.WriteLine("Doesn't write code.")
End If
' Set the current application principal
information to another known user
user = Membership.GetUser("User2")
identity = New
GenericIdentity(user.UserName)
principal = New RolePrincipal(identity)
Threading.Thread.CurrentPrincipal = principal
' Use the
principal to check for role information
Console.Write(Threading.Thread.CurrentPrincipal.Identity.Name)
If Threading.Thread.CurrentPrincipal.IsInRole("Developer") Then
Console.WriteLine("
is a developer.")
Else
Console.WriteLine("
doesn't write code.")
End If
Console.ReadLine()
End Sub
End
Module
Complete App.Config
<?xml
version="1.0"
encoding="utf-8"
?>
<configuration>
<system.web>
<roleManager
enabled="true"
/>
<membership
<providers>
<remove
name="AspNetSqlMembershipProvider"/>
<add
name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider,
System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="false"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="1"
minRequiredNonalphanumericCharacters="0"
passwordAttemptWindow="10"
passwordStrengthRegularExpression=""
/>
</providers>
</membership>
</system.web>
</configuration>
Maurice de Beijer is an independent software
developer, beta tester and a recipient of the MVP award. He specializes in
.Net, object orientation, Visual FoxPro and solving technically challenging
problems. Maurice is The Problem Solver and can be reached at mauricedb@computer.org or at www.TheProblemSolver.nl.