Anatoly Lubarsky Logo
programming, design, integration, games, music

Asp.Net: Authentication based on HTTP Basic protocol. HttpModule.

Author: Anatoly Lubarsky
Published: 15/11/2002
on aspnetmania.com


Many web developers use regular HTML forms in order to provide the information about user permissions to access pages of certain web-application.


This is probably due to the fact that these forms are well known. Also they can provide and request additional information, along with username and password. As soon as the user is authenticated, the application starts to follow up the session, in order to grant him access to other pages.


However this approach also has a number of disadvantages:


  1. Increases the level of complexity.
  2. Application "transparency": for example, it is often enough just to look into the login page Source to calculate target of form submit and its parameters.
  3. Security vulnerabilities and Cross-site attacks: http://www.cert.org/advisories/CA-2000-02.html
  4. attacks, using different Http Clients

Implementation of HTTP Basic protocol in Asp.Net


In this article, I will show another approach to grant the access to an anonymous user. The authentication system is based on HTTP Basic protocol. I have to mention that the down side of this approach is the fact, that in "basic" the username and the password are passed through HTTP header, but as clear text. This drawback is overcome in HTTP "digest" approach, which is the topic of another article.


Take a look at this window [Update: authentication window screenshot missing]:


Sometimes, in forums over the net, one can see questions like: "How can I show standard modal form: login, password, domain for an anonymous user ?"


If this question is answered at all, the most common answer is: "There are special ISAPI filters to perform this."


The most famous commercial ISAPI filter of that type is Authentix. In theory, you could write it yourself. However, it’ is not as easy as it sounds on one hand and on the other hand, people, having different hosting arrangements are not able to put ISAPI on remote HTTP Server.


Authentication logic, based on HTTP Basic protocol:


Step #1

  1. Check if HTTP "Authorization" header exists, if not go to Step #2.
  2. If exists, we get HTTP "Authorization" header - it is the user input in the modal window. It's a string having syntax like "basic abrakadabrasdfsdfsdabrakadabra=="
  3. Cut the word "basic", and decode the substring, encoded base64. The result is the string username:password
  4. Compare with the values, stored in the database. If everything is fine, return the page. If not, go to step 2.

In mini project, attached, I built the database similar to that described in the article on aspnetmania.com "Creating authorization system, based on roles in Asp.Net application." (slight differences). So, the example will be full, user will have roles, page will have roles also.


Step #2

  1. Return HTTP state code 401 (Unauthorized), and set the header, having the following syntax:
    WWW-Authenticate: BASIC realm="some-name"
  2. this response makes browser show our modal window, asking user to fill username and password for some-name, and try to connect again with username and password, gathered in a single string, encoded base64 (it is encoded automatically here).
  3. base64 logic is very famous, so it is obvious, that security is not the reason for coding the header. Probably it is coded in order to avoid transparency.

Implementation of HTTP Basic Authentication, based on roles in Asp.Net:


I had experience in performing this in pure ASP - another way along with ISAPI.


Decoding from base64 string looks as follows:


'*** Decoding from base64 - VBScript
Function Base64Decode(ByVal base64String)

   Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ
      abcdefghijklmnopqrstuvwxyz
      0123456789+/"

   Dim dataLength, sOut, groupBegin

   'remove white spaces, If any
   base64String = Replace(base64String, vbCrLf, "")
   base64String = Replace(base64String, vbTab, "")
   base64String = Replace(base64String, " ", "")

   'The source must consists from groups with Len of 4 chars
   dataLength = Len(base64String)
   If dataLength Mod 4 <> 0 Then
      Err.Raise 1, "Base64Decode", "Bad Base64 string."
      Exit Function
   End If


   ' Now decode each group:
   For groupBegin = 1 To dataLength Step 4
      Dim numDataBytes 
      Dim CharCounter
      Dim thisChar 
      Dim thisData 
      Dim nGroup 
      Dim pOut
      ' Each data group encodes up To 3 actual bytes.
      numDataBytes = 3
      nGroup = 0

      For CharCounter = 0 To 3
         ' Convert each character into 6 bits of data, 
         ' And add it to
         ' an integer For temporary storage.  
         ' If a character is a '=', there
         ' is one fewer data byte. 
         ' (There can only be a maximum of 2 '=' In
         ' the whole string.)

         thisChar = Mid(base64String, groupBegin + CharCounter, 1)

         If thisChar = "=" Then
            numDataBytes = numDataBytes - 1
            thisData = 0
         Else
            thisData = InStr(Base64, thisChar) - 1
         End If

         If thisData = -1 Then
            Err.Raise 2, "Base64Decode", 
               "Bad character In Base64 string."
            Exit Function
         End If

         nGroup = 64 * nGroup + thisData
      Next

      'Hex splits the long To 6 groups with 4 bits
      nGroup = Hex(nGroup)

      'Add leading zeros
      nGroup = String(6 - Len(nGroup), "0") & nGroup

      'Convert the 3 byte hex integer (6 chars) To 3 characters
      pOut = Chr(CByte("&H" & Mid(nGroup, 1, 2))) + _
         Chr(CByte("&H" & Mid(nGroup, 3, 2))) + _
         Chr(CByte("&H" & Mid(nGroup, 5, 2)))

      'add numDataBytes characters To out string
      sOut = sOut & Left(pOut, numDataBytes)
   Next

   Base64Decode = sOut
End Function

Fortunately, in .Net we can perform the same in 2 lines of code:


   byte[] tempConverted = Convert.FromBase64String(strEncoded);
   string userInfo = 
      new ASCIIEncoding().GetString(tempConverted);

Also, in good old ASP we had to perform complex manipulation of requests and responses (see "basic" authentication logic).


In order to implement HTTP "basic" authentication logic we need some component, that participates in the processing pipeline of all requests for a given ASP.NET application.


In ASP.NET we have a special tool for that kind of purpose - HttpModule.


our HttpModule


Let’s write our own HttpModule, in which we will implement "basic" authentication logic. In order for our class to be referenced as HttpModule, it is enough to implement System.Web.IHttpModule interface inside it, having 2 methods: OnAuthenticateRequest, OnEndRequest, that define Step #1 and Step #2 of Basic authentication logic accordingly.


Ok, let’s see what we have:


/*
###############################################################
#
#    Components/AuthBasic.cs
#
#
###############################################################
*/

using System;
using System.Collections;
using System.Configuration;
using System.Security.Principal;
using System.Text;
using System.Web;


namespace HTTPAuth.Components
{
   // Implementation of IHttpModule interface
   // class AuthBasic
   public class AuthBasic : IHttpModule
   {
      public AuthBasic() { }
      public void Dispose() { }


      public void Init(HttpApplication application)
      {
         application.AuthenticateRequest += 
            new EventHandler(this.OnAuthenticateRequest);
         application.EndRequest += 
            new EventHandler(this.OnEndRequest);
      }


      // First method OnAuthenticateRequest, 
      // that implements authentication logic Step #1
      /*
      ######################################################
      # 
      #   OnAuthenticateRequest
      #
      # 
      ######################################################
      */
      public void OnAuthenticateRequest(object source, 
        EventArgs eventArgs)
      {
         HttpApplication app = (HttpApplication) source;

         // then follow authentication logic
         // get Http header "Authorization"
         // check if not empty
         string authorization = 
            app.Request.Headers["Authorization"];
         if ((authorization == null) || 
            (authorization.Length == 0))
         {
            AccessDenied(app);
            return;
         }

         // check if Http header has the syntax of basic
         authorization = authorization.Trim();
         if (authorization.IndexOf("Basic", 0) != 0)
         {
            AccessDenied(app);
            return;
         }

         // cut the word "basic" and decode from base64
         // get "username:password"
         byte[] tempConverted = 
            Convert.FromBase64String(authorization.Substring(6));
         string userInfo = 
            new ASCIIEncoding().GetString(tempConverted);

         // get "username"
         // get "password"
         string[] usernamePassword = 
            userInfo.Split(new char[] {':'});
         string username = usernamePassword[0];
         string password = usernamePassword[1];

         // compare username, password against 
         // the values, stored in the database
         // if everything is fine, 
         // get user group list from the database
         // and create an instance of GenericPrincipal

         string[] groups;
         if (AuthenticateAgent(app, 
            username, password, out groups))
         {
            app.Context.User = 
               new GenericPrincipal(
                  new GenericIdentity (username, 
                     "HTTPAuth.Components.AuthBasic"), 
                  groups);
         }

         // else, AccessDenied
         else
         {
            AccessDenied(app);
            return;
         }
      }


      // Second method OnEndRequest, 
      // that implements authentication logic Step #2
      /*
      #####################################################
      # 
      #   OnEndRequest
      #
      #  
      #####################################################
      */
      public void OnEndRequest(object source, 
         EventArgs eventArgs)
      {
         HttpApplication app = (HttpApplication) source;
         if (app.Response.StatusCode == 401)
         {
            // Show modal window, 
            // realm string is saved in web.config
            string realm = String.Format("Basic Realm=\"{0}\"",
               ConfigurationSettings.AppSettings
                  ["HTTPAuth.Components.AuthBasic_Realm"]);
            app.Response.AppendHeader("WWW-Authenticate", realm);
         }
      }


      // Restrict access - Unauthorized
      /*
      ######################################################
      #
      #   AccessDenied
      #   401 - Access Denied
      # 
      ######################################################
      */
      private void AccessDenied(HttpApplication app)
      {
         app.Response.StatusCode = 401;
         app.Response.StatusDescription = "Access Denied";

         // write to browser
         app.Response.Write("401 Access Denied");
         app.CompleteRequest();
      }


      // the following method implements user authentication
      // against database, based on roles
      // if everything is fine, returns true and user group list,
      // that we need in order to create an instance of
      // GenericPrincipal
      /*
      ######################################################
      # 
      #   AuthenticateAgent
      #
      #   Authenticates Agent, returns true/false
      #   app      in ;  HttpApplication
      #   User     in ;  username
      #   Password in ;  password
      #   groups   out;  agent groups to create GenericPrincipal
      # 
      ######################################################
      */
      protected virtual bool 
         AuthenticateAgent(HttpApplication app, 
            string username, 
            string password, 
            out string[] groups)
      {
         groups = null;
         int lagentID = 0;
         string lpageURL = "";

         // instance of a class, that works with databse
         // source code attached
         SqlDataProvider dataProvider = new SqlDataProvider();


         // check if the specified agent exists
         // get agent if exists
         lagentID = 
            dataProvider.getAgentByUsernamePassword(username,
               password);
         if (lagentID == 0)
            return false;


         // check if the specified user has groups
         // get agent groups
         ArrayList arrAgentsGroups = new ArrayList();
         arrAgentsGroups = dataProvider.getGroupsByAgentID(lagentID);
         if (arrAgentsGroups.Count == 0)
            return false;


         // check if requested page has groups
         // get pages groups
         lpageURL = app.Request.Path;
         ArrayList arrPagesGroups = new ArrayList();
         arrPagesGroups = dataProvider.getGroupsByPageURL(lpageURL);
         if (arrPagesGroups.Count == 0)
            return false;


         // check if at least one agent group 
         // is in Page Groups List
         // if yes, return true
         string[] pagegroups = 
            (string[])arrPagesGroups.ToArray(typeof(string));
         groups = (string[])arrAgentsGroups.ToArray(typeof(string));

         foreach (string groupagentID in groups)
         {
            foreach (string grouppageID in pagegroups)
            {
               if (groupagentID == grouppageID)
                  return true;
            }
         }
         return false;
      }
   }
}

That’s it. HttpModule is ready. Let’s create a configuration repository of it. In Web.config under system.web:


   < httpModules >
      < add name="BasicAuthenticationModule" 
         type="HTTPAuth.Components.AuthBasic, HTTPAuth" / >
      

Syntax:


   < httpModules >
      < add name="ModuleName"
         type="MyNameSpace, MyAssembly" / >
      

Cancelling built-in authentication


   < authentication mode="None" / >
      < authorization >
         < deny users="?" / >
      

Saving realm string under appSettings key


   < appSettings >
      < add key="HTTPAuth.Components.AuthBasic_Realm"
         value="Protected System" / >
   

That’s all.


Class that works with the database and database scripts are attached inside the source code.


Materials and credits: rfc2617, Marty Hall, Greg Reinacker, aspnetmania.com


Related Posts:

Saturday, November 29, 2003 5:40 AM

Comments

# re: Authentication System based on HTTP - Basic protocol. Creating your own .NET HttpModule.
I tried this out but it's not working for me. I get the Auth dialog fine, but I'm never able to put anything but a Windows authorized user into it, before it posts back to the server.

6/14/2004 2:31 PM by Rick Strahl

# re: Authentication System based on HTTP - Basic protocol. Creating your own .NET HttpModule.
In the properties of virtual directory in IIS
directory security - only anonymous checkbox should be checked.

6/14/2004 3:02 PM by Anatoly Lubarsky

Login

Subscribe via RSS

Article Categories

.Net Framework
ASP.NET Tips
C# Win32 API
HTML, CSS, Web
Javascript Tips
MSSQL Tips
System
System.Net
WebServices

Archives

(02) January 2018
(01) June 2013
(03) March 2013
(02) February 2013
(01) July 2012
(01) April 2012
(01) September 2011
(01) August 2011
(03) May 2011
(01) December 2010
(01) November 2010
(01) October 2010
(01) June 2010
(01) May 2010
(02) March 2010
(01) January 2010
(02) December 2009
(03) September 2009
(03) August 2009
(09) July 2009
(04) June 2009
(03) May 2009
(02) April 2009
(03) March 2009
(02) February 2009
(02) January 2009
(04) December 2008
(04) November 2008
(05) October 2008
(04) September 2008
(05) August 2008
(04) July 2008
(05) June 2008
(07) May 2008
(04) April 2008
(03) March 2008
(02) February 2008
(03) January 2008
(03) December 2007
(05) November 2007
(04) October 2007
(05) September 2007
(12) August 2007
(11) July 2007
(14) June 2007
(13) May 2007
(13) April 2007
(10) March 2007
(11) February 2007
(14) January 2007
(14) December 2006
(12) November 2006
(08) October 2006
(09) September 2006
(06) August 2006
(08) July 2006
(10) June 2006
(09) May 2006
(22) April 2006
(25) March 2006
(12) February 2006
(14) January 2006
(19) December 2005
(17) November 2005
(16) October 2005
(16) September 2005
(12) August 2005
(14) July 2005
(09) June 2005
(12) May 2005
(12) April 2005
(20) March 2005
(11) February 2005
(12) January 2005
(18) December 2004
(13) November 2004
(12) October 2004
(14) September 2004
(09) August 2004
(23) July 2004
(19) June 2004
(29) May 2004
(19) April 2004
(16) March 2004
(09) February 2004
(06) January 2004
(02) December 2003
(01) November 2003

Post Categories

.Net and C#
Android
Antispam
App. Development
Architecture
ASP.NET
Blogging
Deprecated Projects
Facebook Platform
Fun
Google
iOS
Javascript
Misc.
MSSQL
Music
My Games
Performance
Roller
Social Networks
Tools
Visual Studio
Web 2.0
WebServices

About Me

linkedin Profile
Recs
Who am I

My Sites

Billy Beet
x2line blogs