Introduction
For a real-world Enterprise application, security plays an important role. Forms-based authentication is a popular technique used by many Web sites. With ASP.NET, writing forms authentication is like a breeze. Forms Authentication provider in ASP.NET exposes cookies-based authentication services to applications. But for some of the applications due to the nature of the security requirements, Forms Authentication provider is not a very good fit. Writing your own Custom Authentication provider offers a solution for such applications. With very little code and effort, you can have a role-based authentication system that is platform-agnostic.
Background: Forms Authentication provider - Not a good fit
Lets take a scenario - An application wants to store the following information for a User:
- Username
- User Primary key (primary Key of the User table)
- E-mail
- User Full name
- Is user an Administrator
- Is user authenticated
- Roles for the User
Forms Authentication provider uses the FormsAuthenticationModule
, FormsIdentity
, FormsAuthenticationTicket
and GenericPrincipal
classes. The application can store the UserName
and roles information in the FormsAuthenticationTicket
. But this application needs to store other information, which cannot be done using FormsAuthenticationTicket
or FormsIdentity
class. To achieve this we can create a custom Identity class by implementing IIdentity
interface. However, on each Request application needs to get the User's data from the underlying data source (SQL Server, XML , LDAP ) and create a CustomIdentity
object with all the details. The code in Login page will be something like this (Listing 1):
Listing 1 - Login.aspx
if(SecurityManager.ValidateLogin(txtUserName.Text, txtPassword.Text))
{
CustomIdentity identity = SecurityManager.GetUserIdentity(
txtUserName.Text);
if(identity != null && identity.IsAuthenticated)
{
ArrayList roles = SecurityManager.GetUserRoles(txtUserName.Text);
CustomPrincipal newUser = new CustomPrincipal(identity, roles);
Context.User = newUser;
FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, False);
}
}
The code in Global.asax will be something like this (Listing 2):
Listing 2 - Global.asax.cs
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookie[
FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
CustomIdentity id = SecurityManager.GetUserIdentity(authTicket.Name);
ArrayList roles = SecurityManager.GetUserRoles(txtUserName.Text);
CustomPrincipal newUser = new CustomPrincipal(identity, roles);
Context.User = newUser;
}
}
For more details on the above example please visit MSDN - How To: Implement Iprincipal
This approach works fine for some applications but think of the unnecessary overhead it has in retrieving the user details on each Request. If your application can live with such a design than be it. But for some of the very interactive, performance savvy applications, this approach is not suitable. Here, writing a Custom Authentication provider serves the purpose.
Custom Authentication module
Implementing the IHttpModule
interface allows you to include custom events that participate in every request made to your application. Custom Authentication can be achieved by implementing IHttpModule
and writing a Custom HttpModule
. I have created a Custom Authentication provider by implementing IHttpModule
. My CustomAuthentication provider has the following classes:
- CustomIdentity.cs - Implements
IIdentity
and contains User details (you can change it as per your requirements).
- CustomPrincipal.cs - Implements
IPrincipal
and represents a Custom Principal object (you can change it as per your requirements).
- CustomAuthenticationModule.cs - Implements
IHttpModule
and handles AuthenticateRequest
event of the HttpApplication
.
- CustomAuthentication.cs - Provides static helper methods like creating a encrypted Authentication string etc.
- CustomEncryption.cs - Utility class for encrypting and decrypting authentication string (you can change it to implement your own encryption algo).
The Custom Authentication provider needs the following entries in Web.Config appSettings
section:
Listing 3 - Web.config
<!---->
<appSettings>
<!---->
<add key="CustomAuthentication.LoginUrl" value="/Login.aspx" />
<!---->
<add key="CustomAuthentication.Cookie.Name" value=".CUSTOM_AUTH" />
<!---->
<add key="CustomAuthentication.Cookie.Timeout" value="2" />
</appSettings>
Sample Application
There is a sample application, which uses this CustomAuthentication module. To add a custom HttpModule
, add the following entries to the Web.config file:
Listing 4 - Web.config
<!---->
<httpModules>
<add name="CustomAuthenticationModule"
type="CustomSecurity.CustomAuthenticationModule, CustomSecurity" />
</httpModules>
Add the required entries for CustomAuthenticationModule as given above in Listing 3. Please see the required code in login page. This is the only code you need to write (Listing 5).
Listing 5 - Login.aspx
if(this.username.Text != "" && this.password.Text !="")
{
ArrayList roles = new ArrayList();
roles.Add("Manager");
if(this.username.Text == "superuser")
roles.Add("Administrator");
roles.Add("ITUser");
System.Text.StringBuilder strRoles = new System.Text.StringBuilder();
foreach(string role in roles)
{
strRoles.Append(role);
strRoles.Append("|");
}
CustomIdentity userIdentity = new CustomIdentity(this.username.Text,
1, true, true, this.username.Text,
"someuser@some.com", strRoles.ToString());
CustomPrincipal principal = new CustomPrincipal(userIdentity, roles);
Context.User = principal;
CustomAuthentication.RedirectFromLoginPage(userIdentity);
}
You can access the CustomIdentity
and CustomPrincipal
object in any aspx page or in any class of middle layer of your application using the following code (Listing 6):
Listing 6 - Home.aspx
this.user1.Text = ((CustomIdentity)Context.User.Identity).UserFullName;
this.user2.Text = ((CustomIdentity)
Thread.CurrentPrincipal.Identity).UserFullName;
role.Text = Thread.CurrentPrincipal.IsInRole("Administrator").ToString();