Building Websites with Microsoft Content Management Server

A fast-paced and practical tutorial guide for C# developers starting out with MCMS 2002

Packt Publishing

 

HOME > CHAPTER 18 > FREE CHAPTER

Chapter 18:
Implementing Forms Authenticaton Free Chapter

Windows authentication is perfect in intranet environments where everyone signs on to the website with the same credentials used to log into Windows. For sites meant for public viewing on the Internet, Windows authentication will not usually be appropriate. Visitors will be prompted for their user name and password and when they do not have an account in the specified domain (e.g. guest users), they won't be able to access content.

The good news is that there are other ways to authenticate a user. ASP.NET supports at least two alternatives. The first uses Microsoft Passport, which users may already use to authenticate against other websites. In order to implement Passport, you have to subscribe to its services, which can be too costly for a small website.

Another alternative is to use Forms authentication, which is the topic of this chapter. Users enter credentials into a form. The web application uses this information to decide whether or not the user has access to the resource they are requesting. Consider implementing Forms authentication when:

·        You have portions of the site that are open to the public and portions that are meant only for a select group of people.

·        You have membership details stored in an external database that contains the information required for authentication.

·        When groups of users do not have Microsoft Windows accounts.

·        When users have Windows accounts but are not authenticated to the same domain as the MCMS server.

About Forms Authentication

Forms authentication is a non-Microsoft-specific mechanism, which existed before ASP.NET. In those days, developers created custom forms consisting of a user name and password field. When a user clicked on the Login button, the form triggered code that checked to see if the user had access to the site. The result was usually stored as a token in the form of a cookie or a session variable. The code that did the checking involved quite a bit of logic. Not only did the developer need to ensure that the code was triggered on all pages that required authentication, he or she also had to manage the state of the token throughout the site.

Today, with ASP.NET technologies, you can find ready-to-use solutions that make implementing Forms authentication much easier.  MCMS has its own flavor of Forms authentication in the form of its own set of classes in the Microsoft.ContentManagement.Web.Security namespace.

Unlike ASP.NET Forms authentication, MCMS Forms authentication performs the additional step of mapping users' credentials to the rights groups they are assigned to.

 

As before, you still have to design the form that contains the user name and password fields. However, instead of building the logic for authenticating the user from scratch you can make use of the classes provided by ASP.NET and MCMS to do the job for you.

How It Works

In order for Forms authentication to work, IIS must allow anonymous access to resources. Otherwise, Windows authentication would take over and visitors without valid credentials would continue to get a network prompt for a user name and password.

Once anonymous access has been enabled, all requests for a page get passed on by IIS to the ASP.NET web application. The application checks to see if the page requires authentication.

Don't forget to ensure that the files in the file system do not have restrictions that prevent the anonymous Internet user account from reading them.

 

When guest access has been enabled in MCMS and the guest account has been granted access to the page, the application serves the page without displaying the login page. Should the page require authentication, the application does another check to see if the user has been previously authenticated by looking for a valid ticket. If a valid ticket is not found, the user is redirected to a login form to enter a user name and password.

The logic to determine whether a user has access to the requested MCMS objects is performed by the Microsoft.ContentManagement.Web.Security.CmsAuthorization module. The user is granted access to a page only if:

·        A valid user name and password have been entered.

·        The user belongs to the group of users specified in the <authorization> tag of the application's Web.config file.

·        The user belongs to a subscriber/author/editor/moderator/template designer/channel manager/administrator rights group assigned to the channel of the requested page.

·        Guest access has been enabled and the guest account has been granted access to the requested page.

Once the user has been successfully authenticated, a ticket is issued in the form of two cookies—an ASP.NET session cookie and an MCMS session cookie. The cookies are set on the client machine. In subsequent attempts to access other pages on the site, the browser will automatically send these cookies to the server. The ASP.NET web application checks the cookies to see if the associated ticket is still valid.

A table that compares different authentication mechanisms for ASP.NET is available at http://msdn.microsoft.com/library/default.asp?url=/library/enus/dnnetsec/html/SecNetAP05.asp.

 

Like regular cookies, both cookies have a lifespan. Through code, you can choose to set a temporary cookie or a persistent cookie. Temporary cookies last only for the duration of the browser session. When the browser is closed, the temporary cookie is removed. Persistent cookies can endure across different browser sessions. It is the responsibility of the server to check any persistent or temporary cookie to see if it is still valid. The ASP.NET cookie has the lifespan specified in the <forms> tag in the Web.config file. The MCMS cookie is kept alive for the duration of time specified in the SCA. Once either cookie expires or is deleted, the user will be directed back to the login page to get authenticated again when a page is next requested.

 

Since cookies are required for Forms authentication to work, the user's browser must allow cookies for the site.

 

To implement Forms authentication, you would need to go through the following steps:

1.       Configure IIS to allow anonymous access for the web application.

2.       Configure the Web.config file of the application.

3.       Create the login page.

If you have portions of the site open for public viewing where it is not appropriate to authenticate users, you would need to perform these additional steps:

4.       Choose or create an MCMS Guest account.

5.       Turn on Guest access for the site using the SCA.

6.       Assign the Guest account subscriber rights to channels meant for public viewing.

We will perform all these tasks to implement Forms authentication for the TropicalGreen site.

Configuring IIS to Allow Anonymous Access

Open Internet Services Manager by selecting Start | Programs | Administrative Tools | Internet Services Manager. In the tree on the left pane, browse to the TropicalGreen virtual directory (e.g. Computer Name | Default Web Site | TropicalGreen).

Right-click on the node that represents your website (e.g. the Default Web Site). Select Properties from the pop-up menu. The Properties dialog appears. Select the Directory Security tab and in the section labeled Anonymous access and authentication control, click the Edit… button.

The Authentication Methods dialog  opens. In the Anonymous access section, ensure that the Anonymous access option is checked.

For Windows 2000, click the Edit… button in the Anonymous access section to view details of the anonymous user account. Ensure that the properties of the anonymous account are set as follows:

Property

Value

User name

IUSR_(Computername)

Replace (Computername) with the name of your computer.

Allow IIS to control password

Checked

 

These are the default settings of IIS. Unless these settings have been changed, you should not need to set them.

With Internet Services Manager open, right-click on the TropicalGreen virtual directory. Repeat the steps above to ensure that anonymous access is configured for that virtual directory.

When you are done, close all open dialog boxes by clicking their OK buttons.

With IIS configured for anonymous access, all requests for pages of the TropicalGreen website will be passed on to ASP.NET.

Configure Settings in the Web.config File

Next we configure the Web.config file of the TropicalGreen project to use Forms authentication.

Open the Web.config file of the TropicalGreen project. Look for the <authentication> tag. By default, it is set to use Windows authentication. Comment out the line <authentication mode="Windows" /> and add in the code highlighted below to change the authentication mechanism to Forms.

<!--  AUTHENTICATION

      This section sets the authentication policies of the application.

      Possible modes are "Windows", "Forms", "Passport" and "None"

-->

<!-- authentication mode="Windows" / -->

 

<authentication mode="Forms">

</authentication>

Between the <authentication> tags, add the <forms> tag. Here, we name the cookie TropicalGreenAuthCookie, set the login form path to /tropicalgreen/Login.aspx (we will build this later), configure the protection level to all and the cookie timeout period to 30 minutes.

<forms name="TropicalGreenAuthCookie"

       path="/"

       loginUrl="/tropicalgreen/Login.aspx"

       protection="All"

       timeout="30">

</forms>

The cookie timeout must be set to the Cookie Lifetime  in minutes set in the SCA. The default setting is 30 minutes. If the two values do not match, you may run into problems when one cookie expires before the other.

For a detailed discussion of what each attribute in the <form> tag means, check out the Microsoft online documentation available at http://www.microsoft.com/resources/documentation/iis/6/all/proddocs/en-us/aaconformselement.mspx.

 

Directly below the closing </authentication> tag, add the <authorization> tag (if it does not already exist). We will add the <allow> tag with the users attribute set to * to allow all users to access the site.

<authorization>

    <allow users="*"></allow>

</authorization>

Finally, check that the <httpModules> section contains a reference to the Microsoft.ContentManagement.Web.Security.CmsAuthorizationModule DLL. This library is automatically added to the Web.config file if you created the project using the wizard or if the project is MCMS-enabled. It contains classes used to map authenticated users to the MCMS rights groups they are assigned to. You can remove the module only when all of the following is true:

·        You have enabled guest access.

·        You only expect guests to view pages on the site. The entire site—all its channels, postings, templates and resources—are available to guests, and there is no need for any kind of authentication.

<httpModules>

<add type="Microsoft.ContentManagement.Web.Security.CmsAuthorizationModule,

      Microsoft.ContentManagement.Web,

      Version=5.0.1200.0,

      Culture=neutral,

      PublicKeyToken=31bf3856ad364e35"

      name="CmsAuthorizationModule" />

. . . code continues . . .

</httpModules>

Save and close the Web.config file.

Creating the Login Page

Once you have configured the TropicalGreen site to use Forms authentication, the first time you view any page within the site, you will be redirected to the form located at /tropicalgreen/login.aspx. However, that file does not exist yet!  Let's create it.

With the TropicalGreen solution open in Visual Studio .NET, add a new web form to the root of the TropicalGreen project and name it Login.aspx. Set the Page Layout to FlowLayout.

Toggle to HTML view. Between the <head> tags add a link to the stylesheet created in Chapter 7.

<LINK href="/tropicalgreen/Styles/Styles.css" type="text/css" 
            rel="stylesheet">

Between the <form> tags, add a table consisting of descriptive labels, textboxes for entering the domain, user name, and password, and a sign in button.

<div align="center">

<table>

<tr>

   <td colspan="2">

      <h1>Enter your Username and Password</h1>

          <hr noshade>

   </td>

</tr>

<tr>

   <td class="BodyText">Domain:</td>

   <td class="BodyText">

      <asp:TextBox Runat="server" ID="txtDomain" Text="">

      </asp:TextBox>

   </td>

</tr>

<tr>

   <td class="BodyText">Username:</td>

   <td class="BodyText">

      <asp:TextBox runat="server" ID="txtUsername">

      </asp:TextBox>

   </td>

</tr>

<tr>

   <td class="BodyText">Password:</td>

   <td class="BodyText">

      <asp:TextBox Runat="server" ID="txtPassword" TextMode="Password">

      </asp:TextBox>

   </td>

</tr>

<tr>

   <td></td>

   <td>

      <asp:Button Runat="server" ID="btnSignIn" Text="Sign In"></asp:Button>

   </td>

</tr>

<tr>

   <td class="BodyText" colspan="2">

      <asp:Label Runat="server" ID="lblErrorMessage" ForeColor="red">

      </asp:Label>

   </td>

</tr>

</table>

</div>

Toggle to Design view. The form appears as shown below.

While in Design view, double-click on the form to get to the code-behind file. Import the Microsoft.ContentManagement.Web.Security namespace. This namespace contains the classes we will use to process the logic required to authenticate the user using Forms authentication. Also import the Microsoft.ContentManagement.Publishing namespace.

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.SessionState;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

 

using Microsoft.ContentManagement.Web.Security;

using Microsoft.ContentManagement.Publishing;

 

namespace TropicalGreen

    { 

       . . . code continues. . .

   }

Toggle back to Design view and double-click on the btnSignIn button. In the btnSignIn_Click() event handler we will string together the user name and domain in the format winNt://(domain)/(username) to form the MCMS user name. Next, we will use the CmsFormsAuthentication.AuthenticateAsUser() method to get an authentication ticket from MCMS. If the user is not a valid MCMS user, the ticket will be null. Any error messages are written to the lblErrorMessage label. Add the following code within the btnSignIn_Click() event handler:

private void btnSignIn_Click(object sender, System.EventArgs e)

{

   string username;

   string domain;

   string password;

   string user;

 

   try

   {

      username = txtUsername.Text;

      domain = txtDomain.Text.ToUpper();

      password = txtPassword.Text;

 

      //string the domain and userName together to get the user's account

      user = "winNt://" + domain + "/" + username;

 

      //get a ticket

      CmsAuthenticationTicket ticket=CmsFormsAuthentication.AuthenticateAsUser 

                                     (user,password);

   }

   catch(Exception ex)

   {

      lblErrorMessage.Text = ex.Message;

   }

}

Once the user has been validated, we will use the RedirectFromLoginPage() method to set the cookie and send the user back to the page he or she was previously viewing. A ReturnUrl querystring is appended to the URL when the user is first brought to the Login page and stores the address of the page the user requested. The RedirectFromLoginPage() method uses the URL stored in the ReturnUrl query string to decide which page to redirect to. Add code in the btnSignIn_Click() event handler as shown below.

private void btnSignIn_Click(object sender, System.EventArgs e)

{

   . . . code continues . . .

   try

   {

   . . . code continues . . .

 

    //get a ticket

     CmsAuthenticationTicket ticket=CmsFormsAuthentication.AuthenticateAsUser 

                                     (user,password);

 

 if(ticket!=null)

 {

   if(Request.QueryString["ReturnUrl"]!=null)

   {

      CmsFormsAuthentication.RedirectFromLoginPage(ticket,true,false);

   }

 }

   

     //the redirection did not occur, the user does not have access

     //to the page

     lblErrorMessage.Text = "Access denied";      

   }

   catch(Exception ex)

   {

      lblErrorMessage.Text = ex.Message;

   }

}

The RedirectFromLoginPage() method does more than redirection. It accepts three parameters:

·        authenticationTicket is the ticket used to generated the cookies.

·        setAspNetCookie decides whether or not an additional ASP.NET cookie will be created. The ASP.NET cookie contains additional information that is not found in the MCMS cookie.

·        createPersistentCookie decides whether or not the cookie will persist across browser sessions. If it is set to false, when the user closes and re-opens the browser or opens another window, he or she will get the Login page again. Once the ticket lifetime as set in the SCA has elapsed, even if createPersistentCookie is set to true, the user will be requested to login again. We are not using this feature, but you can use it to save the login for your users with some tweaking of the code.

With this information, a temporary cookie is set in the memory of the browser. Persistent cookies are stored in the file system.

Next, we will consider the case where the user navigates to http://localhost/tropicalgreen/login.aspx. Unlike the previous case, the ReturnUrl parameter has not been set and therefore cannot be used.

To handle this situation we will use the SetAuthCookie() method. This method is similar to the RedirectFromLoginPage() method, with the main difference being that the SetAuthCookie() method only sets the cookie based on the ticket. It does not perform any redirections.

Add the condition to consider the case where a ReturnUrl query string is not found. In this case, the authentication cookies will be created by SetAuthCookie(). We then explicitly call the Response.Redirect() method to bring the user to the TropicalGreen home page.

private void btnSignIn_Click(object sender, System.EventArgs e)

{

   . . . code continues . . .

   try

   {

   . . . code continues . . .

 

 

     if(ticket!=null)

     {

       if(Request.QueryString["ReturnUrl"]!=null)

       {

         CmsFormsAuthentication.RedirectFromLoginPage(ticket,true,false);

       }

       else

       {

         CmsFormsAuthentication.SetAuthCookie(ticket,true,false);

         Channel root = CmsHttpContext.Current.Searches.GetByPath

                       ("/Channels/TropicalGreen") as Channel;

         if(root!=null)

         {

           Response.Redirect(root.Url);                 

         }

       }

     }  

      . . . code continues . . .

 

   }

   . . . code continues . .

}

The SetAuthCookie() method is typically used when you need to suppress the redirection. The Login page is complete. Save and build the solution. Close login.aspx and its code-behind file.

Since the Login page could be processed before the user has been authenticated by MCMS, ensure that the Login page:

1.    Is not a posting based on a template.

2.    Does not access any CmsHttpContext members, including references to channels,
       template galleries, resource galleries, postings, templates, resources, and users.
       If this is a mandatory requirement then you will have to use the 
      
CmsApplicationContext to access these items.

Otherwise, you may get an error message that says:

Guest access is not enabled for the CMS server. Access is denied.

Or, if guest access has been enabled (see the later part of this chapter for how to do this):

The current user does not have access to the requested item.

Logging on to the Site

Open Internet Explorer and navigate to http://localhost/tropicalgreen/plantcatalog. You should see the login box shown in the following screenshot.

Look at the address bar in Internet Explorer and you will see that the URL of the login page has been appended with a querystring holding a value for a parameter called ReturnUrl. The value stored in the ReturnUrl querystring is a URL-encoded path back to the page that you have requested to see.

If you have set up MCMS to work within a single domain, consider storing the domain name in a centrally managed location such as an application setting in the Web.config file. In this way, you can retrieve its value when calling the CmsFormsAuthentication
.AuthenticateAsUser()
method without users having to enter it each time they fill in the form.

 

Enter your administrative user name and password. For example:

Field

Value

Domain

MCMSBook (replace with the name of your computer/domain name)

UserName

Author1

Password

(the password you have set for this account)

 

If you have entered valid credentials, you will be brought to the Plant Catalog page. Click on any of the plant fact sheets. Notice that you are not asked to enter your credentials again: You will remain authenticated for as long as the cookie lasts.

The CmsFormsAuthentication Class

In the example above, we used the CmsFormsAuthentication.AuthenticateAsUser() method to get the authentication ticket. There are other ways to retrieve this ticket, detailed next.

·        AuthenticateAsCurrentUser()  logs in with the credentials of the current Windows user. Using this method, you do not have to ask for user names and passwords as these are already obtained from the WindowsIdentity.GetCurrent() method. However, because you have configured IIS to allow anonymous access to the site, you will always be using the credentials of the ASPNET account.

·        AuthenticateAsGuest()  logs in with the credentials of the MCMS guest account. If guest account is enabled, the user will be issued a ticket that gives access to portions of the site that are open to guests.

·        AuthenticateUsingWindowsToken()  logs in with the credentials of a Windows user. It is usually used when impersonating a logon session with a Windows account.

·        AuthenticateAsUser()  is the method used in the example above. With this method, you specify the domain, username and password of the user. The format of the user account is WinNT://Domain/Username.

In all these methods, you can choose to pass in optional parameters defining the client account name and client account type. For example, we can specify our own custom values for the user's client account name and client account type by calling:

CmsFormsAuthentication.AuthenticateAsUser 

                 (user,password,"MyAccountName1","MyAccountType1");

In this case, User.ClientAccountName has the value MyAccountName1. User.ClientAccountType holds the value MyAccountType1. This opens the possibility of having multiple users using the same Windows account but mapped to different client accounts.

What's in the Cookie

You can see the contents of the cookie by using the GetAuthCookie() method as shown below:

HttpCookie cook = CmsFormsAuthentication.GetAuthCookie(ticket,true);

Response.Write(cook.Value.ToString());

You will get values such as this:

zne2e42rfjou3mlrhrmpad4opbt2xacv2rq5gm4bctx77slym24ntq5ii3f6ybohuoklqpwxf3g4j

This does not tell you much because the contents have been encrypted. Be assured that the cookie only contains only information that is required for the RedirectFromLoginPage(), SetAuthCookie(), and GetAuthCookie() methods to work. It does not contain sensitive information like the Windows token and password, or other information that may compromise the security of the system.

When Users Do Not Have Rights to View the Requested Page

Should you visit a page you have no access to, you would be returned to the login page. Unless you enter the credentials of a user who has the rights to view the page, you will keep being returned to the login page. To provide a way out, it is a good idea to add a hyperlink that leads the visitor from the login page to the referring page or at least to a part of the site that is accessible.

With Login.aspx opened in Design view, drag and drop a Hyperlink control from the Web Forms Toolbox onto the form as shown in the following figure:

Give the Hyperlink the following properties:

Property

Value

ID