
Introduction
My first article in this series presented a library for managing user-level
credentials. [^]
The next article talked about the internals of that library, and demonstrated
how to extend things by building a data access library for use with SQL-Server.
[^] This article
will demonstrate how to go about using the security library in a typical ASP.NET
web application.
Using the code
For this article I created a simple web application that contains a login
page and a default page. The login page is shown in the screen shot above. The
code for the login page is shown here:
public class LoginPage : System.Web.UI.Page
{
protected System.Web.UI.HtmlControls.HtmlInputText m_userName;
protected System.Web.UI.HtmlControls.HtmlInputCheckBox m_checkboxRemember;
protected System.Web.UI.HtmlControls.HtmlInputButton m_buttonSubmit;
protected System.Web.UI.WebControls.Label m_labelMessage;
protected System.Web.UI.HtmlControls.HtmlInputText m_password;
private void Page_Load(object sender, System.EventArgs e)
{
m_labelMessage.Text = "";
}
private void m_buttonSubmit_ServerClick(object sender, System.EventArgs e)
{
if (!SecurityManager.Authenticate(m_userName.Value, m_password.Value))
{
m_labelMessage.Text = "failed to authenticate!";
return;
}
FormsAuthentication.SetAuthCookie(
m_userName.Value,
m_checkboxRemember.Checked
);
FormsAuthentication.RedirectFromLoginPage(
m_userName.Value,
m_checkboxRemember.Checked
);
}
private void _DoNothing() { }
}
Clicking the "login" button causes the
m_buttonSubmit_ServerClick handler to be called. As you can see,
the method uses the SecurityManager class to authenticate whatever
credentials are supplied by the user. Assuming the credentials pass, the
FormsAuthentication class is then used to set the authentication
cookie and redirect the user to the default page.
Once the user is authenticated the next step is to create a new
CustomPrincipal object and associate it with the user. There are
several different ways to go about doing that, but what I usually do is populate
the Application_AuthenticateRequest event handler, in the
Global.asax file, with code like this:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
return;
Context.User = new CustomPrincipal(
new CustomIdentity(Context.User.Identity.Name
)
);
}
At this point all the hard work is done. The user is authenticated, the
current HttpContext contains an instance of our
CustomPrincipal and CustomIdentity objects. All that
is left to do is demonstrate that we have accomplished something. For that, I
added some code to the default page that dumps various user related properties
to the page at runtime. The screen shot (after authentication) looks like
this:

The code for the default page looks like this:
public class DefaultPage : System.Web.UI.Page
{
private void Page_Load(object sender, System.EventArgs e)
{
if (!Context.User.Identity.IsAuthenticated)
Response.Redirect("login.aspx");
CustomPrincipal cp = (CustomPrincipal)Context.User;
CustomIdentity ci = (CustomIdentity)cp.Identity;
Response.Write("<h2>Identity Properties:</h2>");
Response.Write("<p><b>AuthenticationType:</b> "
+ ci.AuthenticationType + "</p>");
Response.Write("<p><b>CreateDate:</b> " + ci.CreateDate.ToString() + "</p>");
Response.Write("<p><b>IsAuthenticated:</b> " + ci.IsAuthenticated + "</p>");
Response.Write("<p><b>LastLogin:</b> " + ci.LastLogin.ToString() + "</p>");
Response.Write("<p><b>Name:</b> " + ci.Name + "</p>");
Response.Write("<p><b>UserID:</b> " + ci.UserID + "</p>");
Response.Write("<hr>");
DataTable dt = UserRoleManager.FindByUser(ci.UserID).Tables[0];
Response.Write("<h2>User Roles:</h2>");
foreach (DataRow row in dt.Rows)
Response.Write((string)row["role_name"] + "<br>");
string[] rights = SecurityManager.EffectiveRights(ci.UserID);
Response.Write("<h2>Effective Rights:</h2>");
foreach (string right in rights)
Response.Write(right + "<br>");
Response.Write("<hr>");
Response.Write("<h2>Principal Properties:</h2>");
dt = RoleManager.FindAll().Tables[0];
foreach (DataRow row in dt.Rows)
{
string roleName = (string)row["role_name"];
if (cp.IsInRole(roleName))
Response.Write("<b>" + roleName + ": IsInRole = true</b><br>");
else
Response.Write("<b>" + roleName + ": IsInRole = false</b><br>");
}
dt = RightManager.FindAll().Tables[0];
foreach (DataRow row in dt.Rows)
{
string rightName = (string)row["right_name"];
if (cp.IsAuthorized(rightName))
Response.Write("<b>" + rightName + ": IsAuthorized = true</b><br>");
else
Response.Write("<b>" + rightName + ": IsAuthorized = false</b><br>");
}
}
}
The Page_Load method first checks to verify that the user is
authenticated. If not, the user is redirected to the login page.
After that check, the CustomPrincipal and
CustomIdentity objects are recovered from the current
HttpContext and are used to populate the GUI. I also dumped the
list of related user roles and effective rights - just to give the demonstration
some additional umph.
Configuration
The security library requires some changes to the Web.config file. These
changes are needed so that the library can decide how to access its backend
database. The following code was added to the Web.config file:
<!---->
<configSections>
<sectionGroup name="CG.Security.Data">
<section name="runtimeSetup"
type="CG.Security.Data.Configuration.DataSettingsHandler, CG.Security" />
</sectionGroup>
</configSections>
<!---->
<CG.Security.Data>
<runtimeSetup defaultSection="Access">
<installedAssembly>
<add
sectionName="Access"
targetAssembly="CG.Security"
targetNamespace="CG.Security.Data.Access"
connectionString="Provider=Microsoft.Jet.OLEDB.4.0;
User ID=Admin;Data Source=c:\temp\security.mdb;
Mode=Share Deny None;Extended Properties="";Jet OLEDB:System
database="";Jet OLEDB:Registry Path="";Jet OLEDB:Engine Type=5;
Jet OLEDB:Database Locking Mode=1;
Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk
Transactions=1;Jet OLEDB:Create System Database=False;
Jet OLEDB:Encrypt Database=False;
Jet OLEDB:Don't Copy Locale on Compact=False;
Jet OLEDB:Compact Without Replica Repair=False;
Jet OLEDB:SFP=False"
/>
</installedAssembly>
</runtimeSetup>
</CG.Security.Data>
The configuration section is used by the security library to locate the
proper set of data classes to load at runtime. (since the library can be used
with a variety of databases). The section also contains the ADO.NET connection
string for the library.
IMPORTANT NOTE: you will need to modify the connection string to point
to wherever you decided to copy this project on your computer. For instance, if
you copy this project to your c:Inetpub/wwwroot/securitydemo folder then you
should put the path: 'c:\Inetpub\wwwroot\securitydemo\' before the
'security.mdb' portion of the connection string.
If you don't do this then the library will not be able to locate the database
file. Remember this step - it's important!!!
Conclusion
Of course, you can always handle user authentication and authorization the
old fashioned way, but after you write that same block of code for the umpteenth
time you may start to understand why I created the security library in the first
place. Keep in mind that the library will also work with thick-client
applications.
I hope you will enjoy the library - no matter how you decide to use it.
Have fun! :o)