Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security

WinForms - Using a custom principal with AspNetDb

4.40/5 (5 votes)
1 Jun 2010CPOL3 min read 52.8K   1.8K  
Describes how to use a custom principal implementation with the AspNetDb security database in a WinForms application.

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.

AppName.png

Fig. 1 - Create your application name

UserName.png

Fig. 2 - Create users in the application

UserRoleAssign.png

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

C#
namespace System.Security
{
    using System;
    using System.Collections;
    using System.Configuration;
    using System.Security.Principal;
    using System.Data;
    using System.Data.SqlClient;

    /// <summary>
    /// Implements a custom Identity class for use with the AspNetDb
    /// </summary>
    [Serializable()]
    public class AspNetDbIdentity : IIdentity
    {
        string usernameField = string.Empty;
        ArrayList rolesList = new ArrayList();

        #region IIdentity

        /// <summary>
        /// Implements the IsAuthenticated property
        /// defined by IIdentity. Returns True
        /// if the user is a member of at least 1 role
        /// </summary>
        bool IIdentity.IsAuthenticated
        {
            get
            {
                return (rolesList.Count > 0);
            }
        }

        /// <summary>
        /// Implements the AuthenticationType property defined by IIdentity.
        /// </summary>
        string IIdentity.AuthenticationType
        {
            get
            {
                return "AspNetDb";
            }
        }

        /// <summary>
        /// Implements the Name property defined by IIdentity.
        /// </summary>
        string IIdentity.Name
        {
            get
            {
                return usernameField;
            }
        }

        #endregion

        internal bool IsInRole(string role)
        {
            return rolesList.Contains(role);
        }

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="userName">The name of the user defined in the AspNetDb
        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:
  • XML
    <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.
  • XML
    <appsettings>
        <add key="ApplicationName" value="Harmony">
      </add>
    </appsettings>

Implementation of IPrincipal

C#
namespace System.Security
{
    using System;
    using System.Security.Principal;
    using System.Threading;

    /// <summary>
    /// Implements a custom Principal class that used the AspNetDb database
    /// to check for role membership
    /// </summary>
    [Serializable()]
    public class AspNetDbPrincipal : IPrincipal
    {
        AspNetDbIdentity identityField;

        #region IPrincipal

        /// <summary>
        /// Implements the Identity property defined by IPrincipal.
        /// </summary>
        IIdentity IPrincipal.Identity
        {
            get
            {
                return identityField;
            }
        }

        /// <summary>
        /// Implements the IsInRole property defined by IPrincipal.
        /// </summary>
        bool IPrincipal.IsInRole(string role)
        {
            return identityField.IsInRole(role);
        }

        #endregion

        #region Ctor

        /// <summary>
        /// Default construct
        /// </summary>
        /// <param name="identity">The AspNetDbIdentity assigned to the user
        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
            {
                // failed, but we don't care because there's nothing
                // we can do in this case
            }
            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.

C#
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:

C#
// This checks that the AspNetDbPrincipal is a member of the administrator role
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.

TestAppScreenShot.png

History

  • June 2010 - Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)