On the Go with SSO

Create a Single Sign-On App Using Forms Authentication, HTTP Modules, and SQL Server

Russell Lum

October 30, 2009

15 Min Read
ITPro Today logo in a gray background | ITPro Today

asp:Feature

LANGUAGES:VB.NET

ASP.NETVERSIONS: 1.1

On the Go with SSO

Create a Single Sign-On App Using Forms Authentication,HTTP Modules, and SQL Server

By Russell Lum

This article illustrates how you can use ASP.NET sbuilt-in forms authentication to create a Single Sign-On (SSO) application. Ituses a SQL Server database to store and retrieve permissions and an HTTP moduleto catch events between applications. This model can be incorporated intoexisting applications without any code changes. There are four parts to theproject: the database, the SSO site, the HTTP module, and the web.configsettings. Let s get started.

Part I: The Database

Create a common database to hold all your users andpermissions. Figure 1 shows a simple version of the objects.


Figure 1: A simple database example.

The tables represent the minimum number of objects for thesample. There are a plethora of articles that cover this subject in greatdetail. You can replace these structures with your own existing model. Takenote of the SSOEnabled, ProductionName, and AppDesc fields in the Applicationstable. These will need to be added to your database if you are going to add SSOto an existing database.

Next, create the structures for the SSO system. Becausethere is only one session in SSO - but many applications - we only need asingle token.


Figure 2: There is only one sessionin SSO, but many applications.

The token in the AppTokens table consists of the IPaddress of the user who requested the site and the user s SessionID. Includethe UserName, CreatedDate, and SessionEndTime for tracking statistics. TheAppTokensDetail table will hold the application ID that the user has requestedin his/her session and an active bit to track which application is active atany given time (again, see Figure 2). Next, create a few stored procedures forthese tables. We need to be able to perform the following:

  • Add, get, and delete tokens

  • Update token sessions, sign out of sessionsbased on a token

  • Get SSO applications, get SSO applications byuser

  • Get application names by ID

The stored procedures necessary to perform these tasks arein the SQL script included with the sample download accompanying this article(see end of article for details).

And now, on to the next step ... site creation.

Part II: The SSO Site

The SSO site is a central login site that will redirectthe user to any requested page in other applications. It also acts as adestination for users providing a launch site to other applications.

Create a Web project and call it SSO. The site consists ofthe following Web forms: Default.aspx, Login.aspx, NewUser.aspx, anddefaultError.aspx.

Create a folder under the SSO root and call it Secure. Adda web.config file to this directory and place the NewUser.aspx page in it. Thispage will allow anonymous access so new users can be added to the database. Addthe settings to allow this in the web.config file using the under an section.

Add the controls to the NewUser.aspx page that allow theuser to create a new account. Create a dropdown for the application; a Savebutton; and user name, first name, last name, password, and passwordconfirmation textboxes. Add the information to the database using a direct callto a stored procedure. The idea is that the user will get a set of read onlypermissions until an administrator grants more privileges.

Let s wire up some events to our controls. In the clickevent for the Save button write the information to the database. Encrypt thepassword using the HashPasswordForStoringInConfigFile method, which produces aone-way hash. Call the AddNewUser method, which calls a stored procedure toinsert the hashed password, user name, and application ID to the database. Thebutton save click event is written as shown in Figure 3.

Private Sub btnSave_Click(ByVal sender As System.Object, _

                         ByVal e As System.EventArgs)

                         Handles btnSave.Click

 Dim Result As String

 If Not Page.IsValid ThenExit Sub

 Dim SSOAuth As New SSO.Authentication()

 ' encrypt the password

 Dim passEncrypt As String=

 FormsAuthentication.HashPasswordForStoringInConfigFile( _

  tbPassword.Text.ToLower,"md5")

 ' add the user to thedatabase

 Result =AddNewUser(tbUserName.Text, passEncrypt, _

    ddApplications.SelectedItem.Text)

 ' redirect the user tothe login page

 Response.Redirect("../login.aspx" & _

                   QueryStringFromSession())

End Sub

Figure 3: Thebutton save click event.

The users must add a new account for themselves to thespecific application before they can log in. The application dropdown list ispulled from the application table and bound to the dropdown list. Add a Cancelbutton and wire it up with the following code:

Private Sub btnCancel_Click(ByVal sender As System.Object, _

                           ByVal e As System.EventArgs)

                           Handles btnCancel.Click

 Dim querystr As String =QueryStringFromSession()

 If querystr ="" Then

     Response.Redirect("../login.aspx")

 Else

     Response.Redirect("../login.aspx" & querystr)

 End If

End Sub

Note the QueryStringFromSession call. This will bediscussed shortly. Add some validator controls to check for required fields andpassword comparison. Finish it with a validation summary control (see Figure4).


Figure 4: Create a new account.

On the Login.aspx page we need the ability for a username, password, and a New Account button (see Figure 5).


Figure 5: The Login.aspx page.

Wire up the New Account button to redirect to the newaccount page. When the user clicks the Login button, verify: 1) that the userexists; 2) what the user s permissions are; and 3) redirect them to therequested site. When the login page loads, check for a query string. If one ismissing, build a query string to the default page url and append it to aredirect back to the login page. Next, store the query string in a sessionvariable. This needs to be done to capture the redirect query string andprevent the user from overtyping the query string and refreshing the page. Italso allows restoration of the string to the url if the user visits the newuser page. This is where the QueryStringFromSession call from earlier is set,and why it is called in the cancel event. Finally, add some JavaScript to set thefocus of the login textbox control.

To wire up the Login button click event, perform a call tothe database to validate the user. After the request has been validated, anauthentication ticket and token are needed. First, create a login token in theAppTokens table using a stored procedure call. To get the IP address, call aReadOnly property that returns the string of the requestor s IP address bylooping through all of the HostIP s address lists and building a single stringof the addresses (see Figure 6).

Private Shared ReadOnly Property GetIPKey() As String

 Get

   Dim IPAddr As IPAddress

   Dim IPKey As String

   ' pull the hostname

   Dim Host As String =Dns.GetHostName

   Try

   ' pull the ip entries

   Dim HostIP AsIPHostEntry = _

   Dns.GetHostByName(Dns.GetHostName)

   ' loop through thehostip list as can be wrapped

     For Each IPAddr InHostIP.AddressList

   ' create unique keystring

     IPKey +=Replace(IPAddr.ToString, ".", "")

   Next

 Catch

   Return"nokey"

 End Try

   Return IPKey

 End Get

End Property

Figure 6: Call aReadOnly property that builds a single string of the addresses.

The IPKey and application ID are used to validate the user srequest against the database. Next, create a FormsAuthenticationTicket objectand attach it to the response:

Context.Response.Cookies(cookieName).Value =

 FormsAuthentication.Encrypt(ticket).

Note that the code to create the tickets is located in theHTTPModule, which will be discussed later. Once the ticket is added to theresponse, redirect the request to its specified url using theFormsAuthentication.RedirectFromLoginPage method. Specify false for the createpersistent cookie parameter.

If the user requested to log in to SSO itself, they willbe directed to the Default page. This is a simple datalist of applications thatacts as a launcher page for all applications. Links to those applications and avisible indicator of the user s access to those applications are displayedbelow each application image (see Figure 7).


Figure 7: A visible indicator of theuser s access to those applications are displayed below each application image.

Finally, the defaultError page is simply the friendlyredirect page when errors occur. There are two user controls to simplify thecode: an application list control (AppList.ascx) and a header control(Header.ascx). The AppList control displays a list of applications and a tooltip of the descriptions on the login and new user pages. The header controlhandles the look and feel and provides information about the site requested andsite location.

Part III: The HTTP Module

If you have never created an HTTP module, after thissection I am sure you will come up with many uses for them. HTTP modules arevery powerful and elegant taps into the HTTP pipeline. See Using Applicationobject events (ASP.NET) by Dr. Nitin Paranjape referenced at the end of thisarticle for a good, easy-to-understand explanation of the events that can betrapped. In our case, we want to tap into two events: OnBeginRequest andOnPreRequestHandlerExecute. Let s start with OnBeginRequest.

The biggest problem with an SSO model is determining whichapplication and which user is requesting resources, and if that user haspermissions to access those resources. By tapping into the HTTP pipeline, wecan push into and pull from the request the information needed to make thosedeterminations. Create a new class project and call itAuthenticationHttpModule. Add the following code block:

Public NotInheritable Class AuthenticationHttpModule : _

 Implements IHttpModule

 Public Sub Init(ByValapplication As HttpApplication) _

 Implements IHttpModule

 End Sub

 Public Sub Dispose()Implements IHttpModule.Dispose

 End Sub

End Class

Guess what? You ve just created a skeleton class for anHttp module! The following code will trap for our event:

Private Sub OnBeginRequest(ByVal source As Object, _

 ByVal e As EventArgs)

End Sub

Next, let s add our handler to the Init subroutine so itwill look like this:

Public Sub Init(ByVal application As HttpApplication) _

 Implements IHttpModule

 AddHandlerhttpApp.BeginRequest, _

   AddressOfMe.OnBeginRequest

End Sub

We now have an HTTP module and an event handler for theOnBeginRequest event. For the code to know which application is requestinginformation, we will attach a simple cookie to the request. We want theapplication ID, the application name, and the requestor s IP address. Thisinformation will be read out of the requesting application s web.config file.Our subroutine should now look like Figure 8.

Private Sub OnBeginRequest(ByVal source As Object, _

                          ByVal e As EventArgs)

 httpApp =DirectCast(source, HttpApplication)

 ' get the web configsettings

 configs =CType(ConfigurationSettings.GetConfig("WebUIApp"), _

  Specialized.NameValueCollection)

 ' create a new cookie

 Dim cookie As NewHttpCookie("AppInfo")

 ' add the applicationidand name to the cookie from the

 ' apps web config

 cookie.Values.Add("AppID", configs.Get("Web.AppID"))

 cookie.Values.Add("AppName",configs.Get("Web.AppName"))

 cookie.Values.Add("IPKey", Me.GetIPKey)

 ' add the cookie to thecurrent request

 httpApp.Request.Cookies.Add(cookie)

End Sub

Figure 8: Get theapplication ID, the application name, and the requestor s IP address.

GetIPKey is the property, explained earlier, in the Loginbutton click event. The main event we are going to use is theOnPreRequestHandlerExecute. Listing One shows theevent handler code.

This event allows us to tap into the requestingapplication s session and acquire the session ID. The session ID is the uniqueindicator for the token. The event fires before the default HTTPHandlerprocesses the request, so it is the perfect place to determine if the user isauthenticated and what permissions he/she has. Now we need to perform a fewchecks:

  • If the request path ends with login.aspx andit is a GET request, then we want to sign the user out of any sessions andexit. This forces a login to the SSO site.

  • If the application requested is SSO itself, weexit the routine; if the request type is a POST, we exit out. We only want toauthenticate GET requests, not SSO, because there are no permissions that applydirectly to the SSO site. Every user has access to the SSO site.

  • If the user is not authenticated or the databasetoken is not valid, then the session will be abandoned and the request will beredirected to the login page.

  • The final check is a validation of theSessionActive database flag in the AppTokensDetail table. If that is notactive, then the requested application is different from the previous requestand we need to change permissions and the active flag for the new applicationrequested.

If a different application is making the request, we don need another token. Add a new row to the token details and set it to active forthe application. However, permissions need to be changed. Changing permissionsis important because a user may be an administrator in one application and haveread-only access to the other. If you are using a single session object withthe same key name, then you must reload the permissions. Changing permissionsis accomplished when the application token flag does not match the request sapplication ID. This model goes under the assumption that the user permissionsare stored in a session object. The call to SSOAuth.ClearSessionItems in theOnPreRequestHandlerExecute method loops through all the items in the sessionobject of the request s application. It searches for a session key, such as perm ,and will clear out that session. The session will then be filled with the newpermission data for the requested application.

The handler assembly is separated into four classes:

1) A token class handles all aspects of the tokens and theinteraction with the database.

2) A serializable AppToken class simply holds the tokeninformation for the requested application.

3) An authentication class takes care of signing in andout of sessions, authentication tickets, and validation routines.

4) Finally, there is an authentication module class thatprocesses all the requests and implements the IHttpModule interface.

At this point, the user is logging in to SSO and the codecreates an authentication ticket. Using forms authentication we redirect themto the requested site. If the user switches to another site, the request thengoes into our OnBeginRequest method and we attach the application-specificcookie info from the requestor. Once session state is acquired, it determinesif the request is authenticated and has a valid token. We then set up the user spermissions in the session and allow the request to continue to theapplication, bypassing the login because the authentication ticket is stillattached. The last piece is to capture the logoff event. The logoff is handledby the OnPreRequestHandlerExecute (again, see Listing One). The call toSSOAuth.SignOutSession resets all of the active flags for the token detailtable, and sets an end session time in the token table.

Part IV: The web.config Settings

The web.config settings will need to be added to anyapplication s config file that will be using this SSO model. The HTTP moduleuses these settings in the application s web.config file to determine whichapplication is requested. Create a new Web application and call it App1. Set upthe forms authentication to point to the SSO site:

 

  loginUrl="/SSO/login.aspx?AppID=1"

  protection="All" timeout="60" />

Notice the loginUrl contains the application ID in thequery string; it points to the SSO site and not the application s site. TheloginUrl tells forms authentication where to go for the login page. The HTTPmodule is done; however, we need to tell the applications to use it. So add thehttpModules section to the applications config file:

 

  name="AuthenticationHttpModule"/>

We need a section for the module to get the SSO settings from,so add the following:

 

  value="/Login.aspx" />

 

  value="SSOSystem" />

 

 

These settings allow for flexibility within the SSO site.If you use a different page name for the login, launch page, virtual root, orcookie name, then you can adjust these settings. The module reads theconnection string settings for the common database out of the request s configfile, so add a data access section for those settings:

 

  value="server=(local);Trusted_Connection=true;

  database=common" />

The last set of settings are specific to the application.Add the application name and the application ID. The application name is usedto determine if the request is for the SSO site or another site, and theapplication ID is used after a user has logged into the SSO system for storingand comparing tokens:

 

 

Simply add the configuration settings to the application sweb.config file and drop the HTTPHandler assembly into the bin directory on theserver. That s all there is to it! The application is now a part of the SSOmodel. If you want to turn off SSO, then comment out the forms authenticationattribute and the httpModules section.

Conclusion

Let s recap. We created simple users, applications, andpermissions structures in SQL Server. We created a Web application to createand sign in users. We created an HTTP module to intercept requests fromapplications and validate the user against a database. Finally, we interceptedthe session object of the application and re-populated the permissions for anew request! All this is using the .NET Framework s built-in formsauthentication model. The key is to point all sites to a central ticket andtrap the incoming requests using the HTTP pipeline.

There are many things you can add to this model. The SSOsite could easily be turned into a portal site. The database stored procedurescan be turned into a Web service. An encryption class can be added to encryptand decrypt the cookie data and connection strings as it passes over the wire.The config settings could be stored in the machine.config. The HTTPHandlerassembly can be signed and installed in the GAC. A custom authentication schemeusing IPrincipal and IIDentity interfaces for users would fit into theHTTPHandler class. A variety of statistics can be captured and reported basedon the sessions, users, and IP addresses that are captured with this model.

There is one caveat to the initial statement that no codechanges need to be made. To accurately capture session end events, aFormsAuthentication.SignOut call needs to be added to the session end event inthe global.asax. The HTTP module cannot pick up the session end event and writeto the database if the sign out method is not called. The accompanying sampledownload contains the database script, module, sample application site, andsample SSO site. Thanks for reading!

References

The sample codereferenced in this article is available for download.

Russell Lum is aconsultant for Ajilon Consulting in Towson, MD. He has 10 years of experiencein software development. He has been architecting, developing, and designingapplications with .NET since Beta1. He has taught classes on ASP.NETand holds an MCSD and MCAD. You can reach him at mailto:[email protected].

Begin Listing One

Private Sub OnPreRequestHandlerExecute(ByVal source As _

 Object, ByVal e AsEventArgs)

 Try

   Dim signOut As Boolean

   ' set the local http variable

   Me.httpApp =DirectCast(source, HttpApplication)

   ' pull the appinfocookie

   Me.appInfo =httpApp.Request.Cookies("AppInfo")

   ' get the data accessconfig settings for

   ' the connection string

   Dim datConfigs AsNameValueCollection = _

     CType(ConfigurationSettings.GetConfig("DatAccess"),

     Specialized.NameValueCollection)

   ' signout of sessionfor database tracking if the

   ' login page isrequested and it is get request

   IfhttpApp.Request.Path.ToLower.EndsWith("login.aspx") _

     AndAlsohttpApp.Request.RequestType = "GET" Then

       Me.SSOAuth = NewAuthentication()

   ' sign out of thesession in the database

       SSOAuth.SignOutSession(Me.appInfo("AppID"), _

         httpApp.Session.SessionID, _ datConfigs.Get(

         "DataAccess.ConnStringCommon"))

       datConfigs =Nothing

       Exit Sub

    End If

    ' validate that we arenot in sso app

    IfMe.appInfo("AppName") = "SSO" Then Exit Sub

   ' only validate getrequests

     If httpApp.Request.RequestType= "POST" Then Exit Sub

   ' validate that the appis in the db and is active

     Me.SSOAuth = NewAuthentication(

      httpApp.User.Identity.Name, httpApp.Session,

      appInfo.Item("AppID"), datConfigs.Get(

      "DataAccess.ConnStringCommon"))

     datConfigs = Nothing

   ' if user is notauthenticated then exit out

       If NotMe.IsAuthenticated(httpApp.Request) Then

   ' user is notauthenticated for this app

       Me.NotAuthenticated()

          Exit Sub

      End If

   ' pull the token

      Dim myToken AsAppToken = SSOAuth.ValidateTokenDB(

        appInfo.Item("AppID"), CStr(

        httpApp.User.Identity.Name), httpApp.Session,

        httpApp.Context,1)

   ' if token is not validthen force logon

       If Not myToken.IsValid Then

   ' user is notauthenticated for this app

       Me.NotAuthenticated()

         Exit Sub

   End If

   ' session is not activeso activate it

     IfmyToken.SessionActive = False Then

   ' activate the session- deactivate any others

      SSOAuth.ActivateAppDB(appInfo.Item("AppID"),

        httpApp.Session.SessionID, True)

   ' clear out the sessionobjects

   ' get the new userpermissions

   ' replace the sessionwith the new permissions

        SSOAuth.GetUserPermissions(myToken.UserName,

          SSOAuth.ClearSessionItems())

 End If

 Catch

   ' do nothing

 Finally

   SSOAuth = Nothing

 End Try

End Sub

End Listing One

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like