Introduction
A number of our ASP.NET applications use the AspNetDb with the SqlRoleProvider for security and group membership. The Membership API that ships with .NET is excellent at avoiding code repetition for this standard authentication behaviour.
Your WinDorms applications can use the same API as an ASP.NET application, but it has to reference the System.Web library.
However, for many applications, this full API is not required. We just need to find out which groups a user is a member of and grant them access to resources depending on membership. This article will demonstrate a technique to use AspNetDb for user role management but bypass the Membership API.
Background Scenario
You could have a situation where you want to use standard Windows identity names but don't want to use Active Directory for the group membership. You want custom control over the role membership. This could be because you want to keep all of your application security in a single place (i.e., the AspNetDb), or you just want to keep application specific security out of the domain. Domain administrators might not want to be in charge of creating and assigning role membership for applications.
Especially for internal company applications, users will want a 'Single Sign On' approach to applications. They sign on to their terminal with their Windows credentials, and that should give them access to all the applications they require; having a separate login for different applications becomes tiresome. Therefore, we already have the 'UserName' part of the authentication process. This is the name specified in their Windows credentials. Now, we just need to provide functionality for testing for group membership.
The Database
If you don't already have an instance of AspNetDb, the following article defines how to create the database.
NB: You'll need to choose a SQL Server instance other than your local machine.
Database Management
We use the IDesign Credentials Manager which is preferable to the IDE based 'Web Application Tool', since it allows anyone to manage the credentials rather than just those with VS2005 IDE installed.
We use a customised version of this application that allows us to manage a remote SQL database (source code to follow with permission).
Create and Assign Users
The Credentials Manager provides an interface for managing the ASP.NET security model. Create your application name, assign users and roles to the application, and then add users into the roles.
Fig. 1 - Create your application name
Fig. 2 - Create users in the application
Fig. 3 - Assign users to roles
Using the Code
Your application should now check the application to see what roles the user is a member of. We can do this with custom implementations of IPrincipal
and IIdentity
. These implementations are based on the code that forms part of the CSLA.Net framework, but they have been customised to use the procedure that provides role management from AspNetDb.
Implementation of IIdentity
namespace System.Security
{
using System;
using System.Collections;
using System.Configuration;
using System.Security.Principal;
using System.Data;
using System.Data.SqlClient;
[Serializable()]
public class AspNetDbIdentity : IIdentity
{
string usernameField = string.Empty;
ArrayList rolesList = new ArrayList();
#region IIdentity
bool IIdentity.IsAuthenticated
{
get
{
return (rolesList.Count > 0);
}
}
string IIdentity.AuthenticationType
{
get
{
return "AspNetDb";
}
}
string IIdentity.Name
{
get
{
return usernameField;
}
}
#endregion
internal bool IsInRole(string role)
{
return rolesList.Contains(role);
}
#region Ctor
public AspNetDbIdentity(string userName)
{
this.usernameField = userName;
this.rolesList.Clear();
using (SqlConnection cn = new SqlConnection(
ConfigurationManager.ConnectionStrings[
"SecurityConnectionString"].ConnectionString))
{
cn.Open();
using (SqlCommand cm = cn.CreateCommand())
{
cm.CommandText = "aspnet_UsersInRoles_GetRolesForUser";
cm.CommandType = CommandType.StoredProcedure;
cm.Parameters.AddWithValue("@ApplicationName",
ConfigurationManager.AppSettings["ApplicationName"]);
cm.Parameters.AddWithValue("@UserName", userName);
using (SqlDataReader dr = cm.ExecuteReader())
{
while (dr.Read())
{
rolesList.Add(dr.GetString(0));
}
}
}
}
}
#endregion
}
}
As you will see in the construct, there are two application settings that you need to define in your App.config file for the application to work. Make sure you set both of these first.
- Define a connection string for your SQL instance hosting the AspNetDb:
<connectionstrings>
<add name="SecurityConnectionString"
connectionstring="Data Source=YOURSERVER;
Initial Catalog=AspNetDb;User ID=MyUser;Password=Password"
providername="System.Data.SqlClient">
</add>
</connectionstrings>
Define a setting for your application name. This should match the application name you have set in AspNetDb.
<appsettings>
<add key="ApplicationName" value="Harmony">
</add>
</appsettings>
Implementation of IPrincipal
namespace System.Security
{
using System;
using System.Security.Principal;
using System.Threading;
[Serializable()]
public class AspNetDbPrincipal : IPrincipal
{
AspNetDbIdentity identityField;
#region IPrincipal
IIdentity IPrincipal.Identity
{
get
{
return identityField;
}
}
bool IPrincipal.IsInRole(string role)
{
return identityField.IsInRole(role);
}
#endregion
#region Ctor
public AspNetDbPrincipal(AspNetDbIdentity identity)
{
AppDomain currentdomain = Thread.GetDomain();
currentdomain.SetPrincipalPolicy(PrincipalPolicy.UnauthenticatedPrincipal);
IPrincipal oldPrincipal = Thread.CurrentPrincipal;
Thread.CurrentPrincipal = this;
try
{
if (!(oldPrincipal.GetType() == typeof(AspNetDbPrincipal)))
currentdomain.SetThreadPrincipal(this);
}
catch
{
}
identityField = identity;
}
#endregion
}
}
Using the Objects
Constructing the objects follows the same pattern as the WindowsPrincipal
and WindowsIdentity
objects. Pass in Environment.Username
to AspNetDbIdentity
, then use the AspNetDbIdentity
instance to create AspNetDbPrincipal
.
AspNetDbIdentity identity = new AspNetDbIdentity(Environment.UserName)
AspNetDbPrincipal principal = new AspNetDbPrincipal(identity);
Application Security Checks
Constructing the AspNetDbPrincipal
object sets the main thread to the AspNetDbPrincipal
instance, so you can use either programmatic or declarative security by accessing the Thread.CurrentPrincipal
object:
Thread.CurrentPrincipal.IsInRole("Administrator");
The Test Application
The test application included with this article demonstrates all of the settings required to implement role based security in your WinForms application using data from AspNetDb.
History
- June 2010 - Initial release.