Click here to Skip to main content
Email Password   helpLost your password?

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:

  1. Username
  2. User Primary key (primary Key of the User table)
  3. E-mail
  4. User Full name
  5. Is user an Administrator
  6. Is user authenticated
  7. 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

//Validate User

//SecurityManager is a helper class of your application

if(SecurityManager.ValidateLogin(txtUserName.Text, txtPassword.Text))
{
    //Get a CustomIdentity object with all the details 

    //for the authenticated user

    CustomIdentity identity = SecurityManager.GetUserIdentity(
          txtUserName.Text);
    if(identity != null && identity.IsAuthenticated)
    {
        //Get user Roles

        ArrayList roles = SecurityManager.GetUserRoles(txtUserName.Text);
        //Create a CustomPrincipal object

        CustomPrincipal newUser = new CustomPrincipal(identity, roles);
        Context.User = newUser;
        //Redirect user to the requested page

        FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, False);
    }
}

The code in Global.asax will be something like this (Listing 2):

Listing 2 - Global.asax.cs

//Implement an Authentication Request Handler to Construct

// a GenericPrincipal Object

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookie[
             FormsAuthentication.FormsCookieName];
    if(authCookie != null)
    {
        //Extract the forms authentication cookie

        FormsAuthenticationTicket authTicket = 
               FormsAuthentication.Decrypt(authCookie.Value);
        // Create an Identity object

        CustomIdentity id = SecurityManager.GetUserIdentity(authTicket.Name);
        //Get user Roles

        ArrayList roles = SecurityManager.GetUserRoles(txtUserName.Text);
        //Create a CustomPrincipal object

        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:

  1. CustomIdentity.cs - Implements IIdentity and contains User details (you can change it as per your requirements).
  2. CustomPrincipal.cs - Implements IPrincipal and represents a Custom Principal object (you can change it as per your requirements).
  3. CustomAuthenticationModule.cs - Implements IHttpModule and handles AuthenticateRequest event of the HttpApplication.
  4. CustomAuthentication.cs - Provides static helper methods like creating a encrypted Authentication string etc.
  5. 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

<!-- Entries required for CustomAuthenticationModule -->
<appSettings>
    <!-- Parameter for the Login page URL (Required) -->
    <add key="CustomAuthentication.LoginUrl" value="/Login.aspx" />
    <!-- Parameter for the Authentication cookie Name (Required) -->
    <add key="CustomAuthentication.Cookie.Name" value=".CUSTOM_AUTH" />
    <!-- Parameter for Timeout for Cookie expiration in minutes 
       (Optional- persist cookie across browser sessions) -->
    <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

<!-- Add a Custom Authentication module -->
<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

//Write your own Authentication logic here

if(this.username.Text != "" && this.password.Text !="")
{
    //Write your own code to get the User Roles

    ArrayList roles = new ArrayList();
    roles.Add("Manager");

    if(this.username.Text == "superuser")
        roles.Add("Administrator");

    roles.Add("ITUser");

    //Convert roles into pipe "|" separated string

    System.Text.StringBuilder strRoles = new System.Text.StringBuilder();
    foreach(string role in roles)
    {
        strRoles.Append(role);
        strRoles.Append("|");
    }

    //Create a CustomIdentity object

    CustomIdentity userIdentity = new CustomIdentity(this.username.Text, 
        1, true, true, this.username.Text, 
        "someuser@some.com", strRoles.ToString());
    //Create a CustomPrincipal object

    CustomPrincipal principal = new CustomPrincipal(userIdentity, roles);
    Context.User = principal;
    //Redirect user

    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

//Get the CustomIdentity in aspx pages from the HttpContext

this.user1.Text = ((CustomIdentity)Context.User.Identity).UserFullName;

//Get the CustomIdentity in any class in middle layer from the Thread

this.user2.Text = ((CustomIdentity)
    Thread.CurrentPrincipal.Identity).UserFullName;
role.Text =  Thread.CurrentPrincipal.IsInRole("Administrator").ToString();
You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralUse with standard Authorization module
Angus-AU
16:50 8 Dec '09  
Some people may want to use this with the standard authorization module (UrlAuthorizationModule), so that you can use the <authorization /> section in web.config to configure which pages are protected and which are not.

To do this,
1. set <authentication mode="None" /> in web.config
2. remove the redirect code in the OnAuthenticate handler in CustomAuthenticationModule (so if the user is not valid, just do nothing)
3. During the request, UrlAuthorizationModule will check if the current pricipal is allowed to access the requested resource. If it is not allowed, UrlAuthorizationModule will set request.statuscode = 401 and call HttpApplication.CompleteRequest.
4. add an event handler for the Application.EndRequest event in CustomAuthenticationModule
5. In that event handler, check if the status code == 401 (unauthorized). If it is, then do your redirect to the login page.
6. Now your authentication is controlled by the CustomAuthenticationModule, but you can still use the standard way of setting your Authorization in web.config!
GeneralRe: Use with standard Authorization module
reginaldo1982
7:30 9 Dec '09  
Hi mate. This is just what I was looking for. When I try and view a restricted page it redirects me to the login page nicely. However, when I then log in it still doesn't let me in and keeps redirecting, even though the login code is working behind the scenes. I suspect I've set up the authorization incorrectly in the web.config; here's what I've got:
<authentication mode="None" />
    <authorization>
      <allow users="*"/>
      <!-- Allow all users -->
    </authorization>
...
<location allowOverride="false" path="RestrictedPage.aspx"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location>

Can you see anything wrong with that? When I view the restricted page it just bounces me back to the login page whether I'm logged in or not. For the record, here's the event handler I added to the http module:

void EndRequest(object sender, EventArgs e)
{
if (((HttpApplication)sender).Response.StatusCode == 401)
{
string loginUrl = ConfigurationSettings.AppSettings[LOGINURL_KEY];
((HttpApplication)sender).Response.Redirect(((HttpApplication)sender).Request.ApplicationPath + loginUrl + "?ReturnUrl=" + ((HttpApplication)sender).Request.Path, true);
}
}

Many thanks

EDIT: Don't worry about this actually. I was doing something stupid in my code!
GeneralRe: Use with standard Authorization module
Angus-AU
14:17 10 Dec '09  
Good to hear you sorted it out Smile .
GeneralRequest Error
Member 2507335
1:23 18 Jul '09  
Hello.
i use FormsAuthentications but i give this error:
if i don't use Request["ID"] in pages beacause raise this Error:
CustomSecurity.CustomPrincipal,CustomSecurity, Version=1.0.3486.25611, Culture=neutral, PublicKeyToken=null


This error is very Bad
GeneralRe: Request Error
serjoka
2:51 28 Sep '09  
Sorry, but... where is the error ?

You posted it some month ago. Did you fixed the problem ?
GeneralRe: Request Error
Member 2507335
4:44 29 Sep '09  
Hello
Yes,I solve this problem and solve it.
Thanks
Generalmissing cookies?
TheMountain
11:04 12 Nov '08  
by the time I get to the CustomAuthenticationModule, HttpContext.Current.Request.Cookies.Count == 0;...and nothing happens. I have a VERY simplified version (since I'm only authenticating, not authorizing) anything at this point.

void OnAuthenticate(object sender, EventArgs e)
{

string cookieName = Globals.Settings.AuthenticationCookieName;
if (cookieName == null || cookieName.Trim() == String.Empty)
throw new Exception(" CustomAuthentication.Cookie.Name entry not found in appSettings section section of Web.config");

app = (HttpApplication)sender;
HttpRequest req = app.Request;
HttpResponse res = app.Response;

if (req.Cookies.Count > 0 && req.Cookies[cookieName.ToUpper()] != null)
{
HttpCookie cookie = req.Cookies[cookieName.ToUpper()];
if (cookie != null)
{
string _loginID = cookie.Value;
CustomIdentity userIdentity = CustomAuthentication.Decrypt(_loginID);
CustomPrincipal principal = new CustomPrincipal(userIdentity, UserRoles.LoadForUser(_loginID));
app.Context.User = principal;
Thread.CurrentPrincipal = principal;
}
}

}

public static void RedirectFromLoginPage(CustomIdentity identity)
{
string cookieName = Globals.Settings.AuthenticationCookieName;
if (cookieName == null || cookieName.Trim() == String.Empty)
throw new Exception(" CustomAuthentication.Cookie.Name entry not found in appSettings section section of Web.config");

string cookieExpr = Globals.Settings.AuthenticationCookieExpiration;

HttpRequest request = HttpContext.Current.Request;
HttpResponse response = HttpContext.Current.Response;

string encryptedUserDetails = Encrypt(identity);

HttpCookie userCookie = new HttpCookie(cookieName.ToUpper(), encryptedUserDetails);
if (cookieExpr != null && cookieExpr.Trim() != String.Empty)
userCookie.Expires = DateTime.Now.AddMinutes(int.Parse(cookieExpr));

userCookie.HttpOnly = true;

response.Cookies.Add(userCookie);

string returnUrl = request["ReturnUrl"];

if (returnUrl != null && returnUrl.Trim() != String.Empty)
response.Redirect(returnUrl, false);
else
response.Redirect(request.ApplicationPath + "/content/default.aspx", false);

}

and my logon button event
protected void btnLogin_Click(object sender, EventArgs e)
{
// authenticate the user
if (string.IsNullOrEmpty(txtUserName.Text) == true || string.IsNullOrEmpty(txtPassword.Text) == true)
{
this.lblLoginMessage.Text = "Username & Password are both required.";
return;
}

CustomIdentity ident = Telligentz.Core.BLOC.User.ValidateUser(txtUserName.Text, txtPassword.Text);
CustomPrincipal princ = new CustomPrincipal(ident, UserRoles.LoadForUser(txtUserName.Text));
Context.User = princ;
if (ident != null)
CustomAuthentication.RedirectFromLoginPage(ident);
}
GeneralAutoPostBack="True"
feifei_2005
17:06 20 Dec '07  
Add a Control and AutoPostBack="True",
The Exception:
[SerializationException: unparsed members "CustomSecurity.CustomPrincipal, CustomSecurity, Version = 1.0.2911.19864, Culture = neutral, PublicKeyToken = null" type. ]

If AutoPostBack="false" is OK!
AutoPostBack
GeneralRe: AutoPostBack="True"
hhxs
23:07 9 Jul '09  
Two types of webcontrols are not allowd to be using for custom authentication. first is Login and second is ScripManager(Atlas). I have tried to use one of the above, but AuthentionRequest will be revoked mang times when I just browse the "Login.aspx". and so far I have not found the solution.
Generaldon`t download!
feifei_2005
0:52 19 Dec '07  
I don`t download!
hello

QuestionAuthentication Mode
sergiosulpizio@yahoo.it
1:51 19 Feb '07  
I get the following exception:

Unable to cast object of type 'System.Security.Principal.WindowsIdentity' to type 'CustomSecurity.CustomIdentity'.

I tried to change authentication mode but it doesn't work..
AnswerRe: Authentication Mode
brharsh
12:13 14 Jun '07  
I get the same error - trying to get this going in 2.0
have the same error in another example casting from FormsIdentity to CustomIdentity



hey

AnswerRe: Authentication Mode
Sergio Pereira
7:08 19 Jul '07  
I haven't tried this, but check that you do not have FormsAuthentication turned on in your web.config.
I think this will turn it off :


... the rest ...
...


_______________________
Sergio Pereira

GeneralRe: Authentication Mode
Byttencourt
1:14 25 Nov '09  
I've try this but still having same error. There is an error when Cassini (Visual Studio Web Server) try to serialize the CustomPrincipal object. Does anybody have a solution for this??
GeneralAnd Logout?
s.belia
22:56 14 Mar '06  
How can I implement the logout function with your customauthentication?
GeneralRe: And Logout?
boonkerz
10:47 10 Sep '06  
I have the same Problem Smile
GeneralRe: And Logout?
BostonIdiot
8:22 4 Oct '08  
Wouldn't removing the cookie serve as logging out?

I am just guessing....
GeneralRe: And Logout?
Vishwamr1506
4:51 25 Nov '09  
Session.Abandon();
FormsAuthentication.SignOut();
Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", ""));
GeneralIsInRole - no longer working?
luzer
12:58 23 Feb '06  
hi

i would like to use the CustomIdentity class, to store a users PK, etc.

but, my code was working with both Windows and Form based auth.

in windows, the Context.User.Identity.IsInRole would work - checking the users AD groups- on the fly.
in Forms, it would check the roles stored in the cookie - that was populated off the DB.

this doesnt work anymore in windows mode.

any suggestion why not?
Generalextensions caught
Jon Eden
6:13 10 May '05  
Hi,

I'm wondering about the extensions captured by this technique. In IIS you set the specific file types that should be caught and handled by the aspnet_isapi.dll. Is there any good reason not to use the wildcard for any unknowns. I'm thinking that if people are uploading pdfs, jpgs, jpegs etc then eventually certain file types will be uploaded that don't get caught and hence a security risk appears....

Any thoughts?

Cheers,

Jon
GeneralHow does this work with web.config authorization
Jimmy S
0:21 30 Oct '04  
Works a treat for authentication, but seems to overide any authorization settings I have in my web.config file. Any ideas on how to make it authenticate only on pages/directories I specify in web.config?
GeneralExpiration Problem
arulraj007
4:09 25 Aug '04  
If I set the CustomAuthentication.Cookie.Expiration = 20 Mins.
Application Expires in 20 mins, no matter what you make server calls or not.
My understanding is If we make server call cookies expiration should be reset.
Can anyone let me know how to fix this problem or am I doing something wrong.
Thanks.

Arulraj
AnswerRe: Expiration Problem
SUPER_ZORRO
1:30 27 Jan '06  
Try add theis code to CustomAuthenticationModule
public void Init(HttpApplication httpapp)
{
this.app = httpapp;
app.AuthenticateRequest += new EventHandler(this.OnAuthenticate);
app.BeginRequest+= new EventHandler(this.OnBeginRequest);
}

void OnBeginRequest(object sender, EventArgs e)
{
app = (HttpApplication)sender;
HttpRequest req = app.Request;
HttpResponse res = app.Response;

if(req.Cookies.Count==0) return;

string cookieName = ConfigurationSettings.AppSettings[CustomAuthentication.AUTHENTICATION_COOKIE_KEY];
if(cookieName == null || cookieName.Trim() == String.Empty)
{
throw new Exception("CustomAuthentication.Cookie.Name entry not found in appSettings section section of Web.config");
}

string cookieExpr = ConfigurationSettings.AppSettings[CustomAuthentication.AUTHENTICATION_COOKIE_EXPIRATION_KEY];
if(cookieExpr == null || cookieExpr.Trim() == String.Empty)
{
throw new Exception("CustomAuthentication.Cookie.Timeout entry not found in appSettings section section of Web.config");
}

HttpCookie cookie = req.Cookies[cookieName.ToUpper()];
if(cookie==null) return;
cookie.Expires = DateTime.Now.AddMinutes(int.Parse(cookieExpr));
}





GeneralConfiguration Error
abee-fish
5:58 19 Aug '04  
I'm getting an error when trying to run my application. It appear that when trying to load the httpModule it searches in the bin directory. Here's the output:

Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: File or assembly name ChangeControl.CustomSecurity, or one of its dependencies, was not found.

Source Error:


Line 68: <!-- The module we use for authentication --> Line 69: Line 70: Line 71: name="CustomAuthenticationModule"/> Line 72: <!-- //ChangeControl.CustomSecurity" /> -->

Source File: c:\inetpub\wwwroot\ChangeControl\web.config Line: 70

Assembly Load Trace: The following information can be helpful to determine why the assembly 'ChangeControl.CustomSecurity' could not be loaded.


=== Pre-bind state information ===
LOG: DisplayName = ChangeControl.CustomSecurity
(Partial)
LOG: Appbase = file:///c:/inetpub/wwwroot/ChangeControl
LOG: Initial PrivatePath = bin
Calling assembly : (Unknown).
===

LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Post-policy reference: ChangeControl.CustomSecurity
LOG: Attempting download of new URL file:///C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/changecontrol/c1cd7e7e/416a3947/ChangeControl.CustomSecurity.DLL.
LOG: Attempting download of new URL file:///C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/changecontrol/c1cd7e7e/416a3947/ChangeControl.CustomSecurity/ChangeControl.CustomSecurity.DLL.
LOG: Attempting download of new URL file:///c:/inetpub/wwwroot/ChangeControl/bin/ChangeControl.CustomSecurity.DLL.
LOG: Attempting download of new URL file:///c:/inetpub/wwwroot/ChangeControl/bin/ChangeControl.CustomSecurity/ChangeControl.CustomSecurity.DLL.
LOG: Attempting download of new URL file:///C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/changecontrol/c1cd7e7e/416a3947/ChangeControl.CustomSecurity.EXE.
LOG: Attempting download of new URL file:///C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/changecontrol/c1cd7e7e/416a3947/ChangeControl.CustomSecurity/ChangeControl.CustomSecurity.EXE.
LOG: Attempting download of new URL file:///c:/inetpub/wwwroot/ChangeControl/bin/ChangeControl.CustomSecurity.EXE.
LOG: Attempting download of new URL file:///c:/inetpub/wwwroot/ChangeControl/bin/ChangeControl.CustomSecurity/ChangeControl.CustomSecurity.EXE.



GeneralRe: Configuration Error
abee-fish
8:19 19 Aug '04  
I found my own solution.

I'm new to .NET and I didn't realize I needed to create a .dll from the CustomSecurity download and then add that to my references.

It all works fine now. I'm just working on the logoff scenario now.

abee


Last Updated 3 Nov 2003 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010