Searching Sites Protected by Forms Authentication with Enterprise Search in SharePoint Server 2007

Summary: Learn to use Enterprise Search to crawl sites that are protected by forms authentication or cookie-based authentication, and walk through creating a sample custom security trimmer for trimming the returned search results. (18 printed pages)

Jo-Anne West, Microsoft Corporation

October 2007

Applies to: Microsoft Office SharePoint Server 2007, Microsoft Office SharePoint Server 2007 for Search

Contents

  • Overview of Crawling Sites Protected by Forms Authentication or Cookie-Based Authentication

  • Security Trimming in Enterprise Search

  • Implementing a Custom Security Trimmer for Sites Protected by Forms Authentication

  • Creating the FormsAuthenticationSearchSample Project

  • Deploying the FormsAuthenticationSearchSample Project

  • Conclusion

  • Additional Resources

Overview of Crawling Sites Protected by Forms Authentication or Cookie-Based Authentication

Microsoft Office SharePoint Server 2007 supports forms authentication that is based on ASP.NET 2.0. However, the released version of Enterprise Search in Microsoft Office SharePoint Server 2007 did not support crawling remote SharePoint sites or other (not SharePoint-based) sites that are protected by forms authentication.

Microsoft has now added support for these site scenarios to Enterprise Search in the pre-SP1 hotfix, which is described in the Knowledge Base article Sites That Require Forms-Based Authentication or Cookie-Based Authentication Are Not Crawled in SharePoint Server 2007.

Note

This pre-SP1 hotfix does not apply to content that is saved on a Microsoft Office SharePoint Server 2007 site or on a Windows SharePoint Services 3.0 site. For these sites, you must configure the Web application default zone to use NTLM authentication to index the sites. For more information, see Prepare to Crawl Host-Named Sites That Use Forms Authentication.

To enable Enterprise Search to crawl sites that are protected by forms authentication, you must do the following:

  1. Install the hotfix. (For the steps to do this, see Sites That Require Forms-Based Authentication or Cookie-Based Authentication Are Not Crawled in SharePoint Server 2007.)

  2. Configure a crawl rule for forms authentication–protected sites by using the AddRule.exe command-line tool, available from the Microsoft Download Center.

The following section describes how to configure the crawl rule by using the AddRule.exe command-line tool.

Using AddRule.exe to Configure Crawl Rules

You must use the AddRule.exe command-line tool to configure the crawl rule for sites protected by forms authentication or cookie-based authentication, because you cannot configure these crawl rules through the Enterprise Search Administration user interface (UI).

Note

After you configure the crawl rule, it appears on the Manage Crawl Rules page; however, you have only the options of deleting the crawl rule or changing the order in which it is applied. No other configuration changes are available.

The AddRule.exe command-line operation takes one parameter, the name of the XML file that contains the crawl rule configuration. The format for the XML file follows.

<rules>
  <rule>
    <path></path>
    <type>FORM/COOKIE</type>
    <error_pages>
      <error_page></error_page>
      <error_page></error_page>
    </error_pages>
    <auth_url></auth_url>
    <login_type>POST/GET</login_type>
    <parameters>
      <param name=""></param>
    </parameters>
    <cookies>
      <cookie></cookie>
    </cookies>
  </rule>
</rules>

Table 1 describes the crawl rule configuration XML file elements.

Table 1. XML file elements that specify crawl rule configuration

Name Description Applies To

rules

Contains all the crawl rules to configure.

Forms authentication

OR

Cookie-based authentication

rule

Contains the elements that define the configuration for a crawl rule.

Forms authentication

OR

Cookie-based authentication

path

URL that this crawl rule applies to.

Forms authentication

OR

Cookie-based authentication

Type

Specifies the authentication type.

Forms authentication

OR

Cookie-based authentication

error_pages

Contains the URLs the crawler can be redirected to.

Forms authentication

OR

Cookie-based authentication

error_page

Specifies the URL that the crawler can be redirected to. For example, the URL for the page a user is redirected to if he or she enters the wrong credentials.

Forms authentication

OR

Cookie-based authentication

auth_url

Contains the URL for the page that sets the forms authentication cookie.

Forms authentication

login_type

Contains the type of request required by the page specified in the auth_url element. Can be POST or GET. If POST is specified, you must specify at least one param element value.

Forms authentication

parameters

Contains the param elements for the request.

Forms authentication

param

Contains a parameter specified for the request.

Forms authentication

cookies

Contains the cookies to append to the request header.

Cookie-based authentication

cookie

Contains the cookie to append to the request header.

Cookie-based authentication

Following is an example of the XML input used to configure a crawl rule for a site protected by forms authentication.

<rules>
  <rule>
    <path>http://YourFormsAuthSite/*</path>
    <type>FORM</type>
    <error_pages>
      <error_page>Logon.aspx</error_page>
    </error_pages>
    <auth_url>Logon.aspx</auth_url>
    <login_type>POST</login_type>
    <parameters>
      <param name="__VIEWSTATE">dDw0OTQzMjI0MjQ7O2w8UGVyc2lzdDs%2BPvhWhKKTnHpM3RIvgkgC9jJVpN%2Bg</param>
      <param name="Login1%24UserName">FormsAuthUserName</param>
      <param name="Login1%24LoginButton">FormsAuthPassword</param>
      <param name="Login1%24LoginButton">Log+In</param>
    </parameters>
  </rule>
</rules>

After you create the XML file that specifies the crawl rule configuration, do the following:

  1. From a command prompt, type the following to run the AddRule.exe command-line tool:

    AddRule.exeXMLFileName

  2. Replace XMLFileName with the name of the XML file you created.

After installing the hotfix described in the Knowledge Base article Sites That Require Forms-Based Authentication or Cookie-Based Authentication Are Not Crawled in SharePoint Server 2007, and configuring crawl rules by using the AddRule.exe command-line tool, the crawler for Enterprise Search can do the following:

  • Access sites protected by forms authentication or cookie-based authentication.

  • Include the accessed sites in the content index.

  • Return the sites in search results.

Note

The built-in security trimmer for Enterprise Search does not support security trimming of search results from sites that are not SharePoint sites and that are protected by forms authentication or cookie-based authentication; in these sites, users might see items displayed in the search results that they do not have access to. This scenario and a solution for it are described in greater detail in the Implementing a Custom Security Trimmer for Sites Protected by Forms Authentication section of this article.

Security Trimming in Enterprise Search

Enterprise Search performs security trimming of search results at query time. The results are trimmed based on the identity of the user who is submitting the query, by using the security information that the search indexing component stores at crawl time. This ensures that a user sees only the results that he or she has access to.

However, the Enterprise Search indexing component stores security information about content items only when it crawls the following content repositories:

  • File share content protected by authentication schemes that are based on Windows

  • SharePoint sites

  • Lotus Notes content

The indexing component is not able to store security information from other types of content repositories, such as the following:

  • Sites protected by forms authentication, or by another type of cookie-based authentication

  • Web sites other than SharePoint sites

  • File share content on systems that are not Windows–based

The built-in security trimmer uses the security information stored by the indexing component at crawl time to trim the search results at query time, based on the search user's identity. However, the built-in security trimmer is not able to trim search results for content from sources where no security information is stored by the indexing component. In this scenario, the search user sees all results, regardless of whether the user has access to all or only some of those results. To provide security trimming in this scenario, you must create a custom security trimmer.

Custom Security Trimmer Overview

Enterprise Search provides support for custom security trimming through the ISecurityTrimmer interface, which is part of the Microsoft.Office.Server.Search.Query namespace. The ISecurityTrimmer interface contains two methods you must implement: the ISecurityTrimmer.Initialize method (Microsoft.Office.Server.Search.Query), and the ISecurityTrimmer.CheckAccess method (Microsoft.Office.Server.Search.Query).

For more information about these methods, the ISecurityTrimmer interface, and implementing custom security trimmers, see Custom Security Trimming Overview and Walkthrough: Using a Custom Security Trimmer for Search Results in the Microsoft Office SharePoint Server 2007 SDK.

The CheckAccess method executes at least once each time a search query returns results that match the crawl rule associated with the security trimmer. One of the parameters passed to this method is a System.Collections.Generic.IList object containing the URL for each content item that matches the crawl rule. The return value for this method is a System.Collections.BitArray object that represents an array of true or false values, one for each content item URL in the IList object that is passed into the method.

When you implement the CheckAccess method for content with security information, you can try accessing each URL contained in the IList object based on the user's identity, and return true or false for that URL in the BitArray, depending on whether access was granted or denied. For URLs that the user has access to, return true; otherwise, return false.

Implementing a Custom Security Trimmer for Sites Protected by Forms Authentication

The design for the custom security trimmer described in this section provides a starting point for creating a custom security trimmer for sites protected by forms authentication. However, you also need the following additional components:

  • A way to request the user's credentials, and then generate the forms authentication cookie by using those credentials

  • A way to store the forms authentication cookie for a user that can be persisted across multiple page requests, so that your users need to enter their credentials only once per browsing session

The requirements for this scenario are similar to the requirements of another familiar scenario: that of ASP.NET session state management. As you determine the best way to implement this scenario for your environment, consider the different session state modes supported in ASP.NET. For example, in a load-balanced environment, you use one of the session state modes that stores the session information outside of the ASP.NET process, such as StateServer mode or SQLServer mode, because there is no guarantee that all the user's page requests will go to the Web server that handled the initial request. By using this reasoning, you can apply the same guidelines when designing your solution to store the forms authentication cookie information.

For more recommendations about ASP.NET session state management that can help you determine how to implement your forms authentication security trimmer, see ASP.NET State Recommendations.

The remainder of this article walks you through creating and deploying a sample solution for trimming results from sites by using forms authentication.

Creating the FormsAuthenticationSearchSample Project

This section walks you through the steps to create the FormsAuthenticationSearchSample project in Microsoft Visual Studio 2005, for the custom security trimmer solution, and includes:

  1. Setting up the project.

  2. Creating the classes, which include the following:

    • FormsAuthenticationInfo  Contains details such as the forms authentication cookie and the expiration status for a specific entry in the forms authentication cache.

    • FormsAuthenticationCache  Contains a collection of the FormsAuthenticationInfo objects for all users whose forms authentication credentials have been validated by the FormsAuthenticationWebPart class.

    • FormsAuthenticationWebPart  Web Part class that obtains and validates the user's forms authentication credentials.

    • FormsAuthenticationSecurityTrimmer  Contains the security trimmer.

Note

The FormsAuthenticationSearchSample project is a basic implementation of the components required for this scenario. To use the sample successfully with your Office SharePoint Server 2007 environment, you might need to modify it to meet your environment's requirements.

To set up the FormsAuthenticationSearchSample project

  1. In Visual Studio, on the File menu, point to New, and then click Project.

  2. In Project types, under C#, click Windows.

  3. Under Templates, click Class Library. In the Name field, type FormsAuthenticationSearchSample, and then click OK.

  4. In Solution Explorer, right-click Class1.cs, and then click Delete to remove the default class created with the project.

  5. On the Project menu, click Add Reference.

  6. On the .NET tab, select each of the following references (click OK after each selection):

    • System.XML

    • System.Web

    • Microsoft Office SharePoint Server Search

To create the FormsAuthenticationInfo class

  1. On the Project menu, click Add New Item.

  2. In the Add New Item dialog box, click Class, type FormsAuthenticationInfo.cs, and then click Add.

  3. Add the following using statement near the top of the code with the other namespace directives.

    using System.Net;
    
  4. Add the following code for the private field members, to provide information about that instance of the FormsAuthenticationInfo object.

    private DateTime startTime;
    private TimeSpan expiryDuration;
    private CookieContainer formsAuthCookieContainer;
    
  5. Add the following code for the FormsAuthenticationInfo constructor.

    public FormsAuthenticationInfo(DateTime start, TimeSpan duration, CookieContainer cookieContainer)
    {
                this.Start = start;
                this.Duration = duration;
                this.AuthenticationCookieContainer = cookieContainer;
    }
    
  6. Add the following code to define the public properties that provide access to the private fields that are defined in Step 4.

    public DateTime Start
    {
        get { return this._StartTime; }
        set { this._StartTime = value; }
    }
    
    public TimeSpan Duration
    {
        get { return this._ExpiryDuration; }
        set { this._ExpiryDuration = value; }
    }
    
    public CookieContainer AuthenticationCookieContainer
    {
        get 
        { 
            return this._FormsAuthCookieContainer; 
        }
        set
        {
            if (value == null) throw new ArgumentNullException();
            this._FormsAuthCookieContainer = value;
        }
    }
    
    public bool HasExpired
    {
        get
        {
            DateTime expiryTime = this.Start.Add(this.Duration);
            if (DateTime.Now >= expiryTime)
                return true;
            else
                return false;
        }
    }
    

To create the FormsAuthenticationCache class

  1. On the Project menu, click Add New Item.

  2. In the Add New Item dialog box, click Class, type FormsAuthenticationCache.cs, and then click Add.

  3. Add the following using statement near the top of the code with the other namespace directives.

    using System.Security.Principal;
    
  4. Add the following code for the private field for the collection of FormsAuthenticationInfo objects and the current user.

    private static IDictionary<string, FormsAuthenticationInfo> AuthInfo = new Dictionary<string, FormsAuthenticationInfo>();
    private string CurrentUserSID = String.Empty;
    
  5. Add the following code to return an instance of the FormsAuthenticationCache object, with the private CurrentUserSID set to the current user.

    public static FormsAuthenticationCache Current
    {
        get {return new  FormsAuthenticationCache(System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString());}
    }
    
  6. Add the AuthenticationInfo property, which returns an instance of the FormsAuthenticationInfo class for the current user.

    public FormsAuthenticationInfo AuthenticationInfo
    {
        get
        {
            if (String.IsNullOrEmpty(this.CurrentUserSID) || !FormsAuthenticationCache.AuthInfo.ContainsKey(this.CurrentUserSID))
                return null;
            return FormsAuthenticationCache.AuthInfo[this.CurrentUserSID];
        }
        set
        {
            if (value == null)
            {
                FormsAuthenticationCache.AuthInfo.Remove(this.CurrentUserSID);
            }
            else
            {
                FormsAuthenticationCache.AuthInfo[this.CurrentUserSID] = value;
            }
        }
    }
    

To create the FormsAuthenticationWebPart class

  1. On the Project menu, click Add New Item.

  2. In the Add New Item dialog box, click Web Custom Control, type FormsAuthenticationWebPart.cs, and then click Add.

  3. Add the following using statements near the top of the code with the other namespace directives.

    using System.Net;
    using System.IO;
    using System.Xml.Serialization;
    
  4. In the class declaration, replace WebControl with WebPart, as follows.

    public class FormsAuthenticationWebPart : WebPart
    
  5. Add the following line of code above the class declaration for FormsAuthenticationWebPart.

    [XmlRoot(Namespace = "FormsAuthenticationSearchSample")]
    
  6. Add the following code below the class declaration.

    /*
     Change YourFormsAuthSite to the name of the site 
     configured to use forms authentication.  
     */
    public static string Url = "http://YourFormsAuthSite";
    private CookieContainer SharedCookies;
    /*
     Change FormsAuthLoginPageName to the name of 
     logon page name for the site protected by forms authentication.  
     */
    private string LogonUrl = Url + " FormsAuthLoginPageName ";
    private TextBox UsernameTextBox;
    private TextBox PasswordTextBox;
    private Button LogOnButton;
    private CheckBox IncludeFormsAuthContent;
    private LiteralControl HelpText;
    private Label displayAuthenticationStatus;
    private Boolean showForm = true;
    private Boolean loggedIn = false;
    private string statusMessage = "";
    private FormsAuthenticationCache currentFormsAuthenticationCache;
    
  7. Override the OnInit event by using the following code.

    protected override void OnInit(EventArgs e)
    {
        currentFormsAuthenticationCache = FormsAuthenticationCache.Current;
    
        if (currentFormsAuthenticationCache != null)
        {
            FormsAuthenticationInfo currentAuthInfo = currentFormsAuthenticationCache.AuthenticationInfo;
            if (currentAuthInfo != null)
            {
                if (currentAuthInfo.HasExpired)
                {
                    showForm = true;
                    currentFormsAuthenticationCache.AuthenticationInfo = null;
                    statusMessage = "Current Status:  Login Session Expired.";
                }
                else
                {
                    showForm = false;
                    statusMessage = "Current Status:  Logged in.";
                    loggedIn = true;
                }
            }
            else
            {
                statusMessage = "Logged out.";
            }
    
        }
        base.OnInit(e);
    }
    
  8. Override the CreateChildControls method by using the following code.

    protected override void CreateChildControls()
    {
        Table table = new Table();
        table.Width = Unit.Percentage(100);
        table.Attributes.Add("cellspacing", "1");
        table.Attributes.Add("cellpadding", "1");
    
        TableRow row = new TableRow();
        TableCell cell = new TableCell();
        IncludeFormsAuthContent = new CheckBox();
        IncludeFormsAuthContent.Text = "Include forms authentication content";
        this.IncludeFormsAuthContent.AutoPostBack = true;
        this.IncludeFormsAuthContent.CheckedChanged += new EventHandler(IncludeFormsAuthContent_CheckedChanged);
        cell.Controls.Add(this.IncludeFormsAuthContent);
        cell.Width = Unit.Percentage(100);
        cell.Attributes.Add("colspan", "3");
        row.Controls.Add(cell);
        row.Width = Unit.Percentage(100);
        table.Controls.Add(row);
    
        row = new TableRow();
        cell = new TableCell();
        HelpText = new LiteralControl("Please enter your form credentials");
        cell.Controls.Add(this.HelpText);
        cell.Attributes.Add("colspan", "3");
        cell.Width = Unit.Percentage(100);
        row.Controls.Add(cell);
        row.Width = Unit.Percentage(100);
        table.Controls.Add(row);
    
        row = new TableRow();
        cell = new TableCell();
        UsernameTextBox = new TextBox();
        cell.Controls.Add(UsernameTextBox);
        row.Controls.Add(cell);
    
        cell = new TableCell();
        PasswordTextBox = new TextBox();
        PasswordTextBox.TextMode = TextBoxMode.Password;
        cell.Controls.Add(PasswordTextBox);
        row.Controls.Add(cell);
    
        cell = new TableCell();
        LogOnButton = new Button();
        LogOnButton.Text = "Log On";
        LogOnButton.Click += new EventHandler(LogOnButton_click);
        cell.Controls.Add(LogOnButton);
        row.Controls.Add(cell);
        row.Width = Unit.Percentage(100);
        table.Controls.Add(row);
    
        row = new TableRow();
        cell = new TableCell();
        displayAuthenticationStatus = new Label();
        cell.Controls.Add(displayAuthenticationStatus);
        cell.Width = Unit.Percentage(100);
        row.Cells.Add(cell);
        row.Width = Unit.Percentage(100);
        table.Rows.Add(row);
    
        this.Controls.Add(table);
        base.CreateChildControls();
    }
    
  9. Add the following code for the CheckedChanged event of the IncludeFormsAuthContent check box. If it is not selected, and the current user has an entry in the FormsAuthenticationCache object that matches his or her user SID, this code removes the credentials from the FormsAuthenticationCache object.

    public void IncludeFormsAuthContent_CheckedChanged(object sender, EventArgs e)
    {
        if (!this.IncludeFormsAuthContent.Checked)
        {
            if (currentFormsAuthenticationCache != null)
            {
                currentFormsAuthenticationCache.AuthenticationInfo = null;
                statusMessage = "Logged out.";
                UsernameTextBox.Text = "";
                PasswordTextBox.Text = "";
                loggedIn = false;
            }
        }
    }
    
  10. Add a click event for the LogOnButton object by using the following code.

    public void LogOnButton_click(object sender, EventArgs e)
    {
        SendRequest(LogonUrl, UsernameTextBox.Text, PasswordTextBox.Text);
        if (this.SharedCookies != null && this.SharedCookies.Count > 0)
        {
            FormsAuthenticationInfo authInfo = new FormsAuthenticationInfo(DateTime.Now, new TimeSpan(0, 30, 0), this.SharedCookies);
            currentFormsAuthenticationCache.AuthenticationInfo = authInfo;
            statusMessage = "Current Status: Logged in.";
            showForm = false;
            loggedIn = true;
        }
        else
        {
            statusMessage = "Authentication failed.  Verify your username, and re-enter your password.";
            PasswordTextBox.Text = "";
    
        }
    }
    
  11. Override the OnPreRender event by using the following code.

    protected override void OnPreRender(EventArgs e)
    {
        displayAuthenticationStatus.Text = statusMessage;
        UsernameTextBox.Visible = IncludeFormsAuthContent.Checked && showForm;
        PasswordTextBox.Visible = IncludeFormsAuthContent.Checked && showForm;
        LogOnButton.Visible = IncludeFormsAuthContent.Checked && showForm;
        HelpText.Visible = IncludeFormsAuthContent.Checked && showForm;
        if (loggedIn)
        {
            IncludeFormsAuthContent.Checked = true;
    
        }
    
        base.OnPreRender(e);
    }
    
  12. Add the following code for the SendRequest method. This method builds the post data, including the forms authentication credentials specified by the user for the request, and then submits the request to the logon page specified for the forms-authentication protected site.

    protected void SendRequest(string url, string username, string password)
    {
    
        //Request the logon page to get the viewstate and validation data.
        HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
        request.Credentials = CredentialCache.DefaultCredentials;
        StreamReader responseReader = new StreamReader(request.GetResponse().GetResponseStream());
        string responseData = responseReader.ReadToEnd();
        responseReader.Close();
    
        //Extract the viewstate and validation data.
        string viewstate = ExtractViewsStateData(responseData, "__VIEWSTATE");
        string eventValidation = ExtractViewsStateData(responseData, "__EVENTVALIDATION");
    
        /*
         * Replace the italicized values with the following values from the logon page:
         * UserInputControlName with the control name for the username text box
         * PasswordInputControlName with the control name for the password text box
         * SubmitControlName with the control name for the submit button
         * SubmitControlValue with the value for the submit button
         */
        string userInputControlName = "UserInputControlName";
        string passwordInputControlName = "PasswordInputControlName";
        string submitButtonControlName = "SubmitControlName";
        string submitButtonValue = "SubmitControlValue";
    
        /*
         * Build the string for the POST request to the logon page
         * with the credentials entered by the user.
         */
        string postData = String.Format(
           "__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE={0}&{1}={2}&{3}={4}&{5}={6}&__EVENTVALIDATION={7}",
           viewstate, userInputControlName, username, passwordInputControlName, 
           password, submitButtonControlName, submitButtonValue, eventValidation);
    
        //Send the POST request to the logon page.
        request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        CookieContainer cookies = new CookieContainer();
        request.CookieContainer = cookies;
        StreamWriter requestWriter = new StreamWriter(request.GetRequestStream());
        requestWriter.Write(postData);
        requestWriter.Close();
        request.GetResponse();
        SharedCookies = request.CookieContainer;
    }
    
  13. Add the following code for the ExtractViewsStateData method, called by the SendRequest method to parse through the initial response received from the server, and extract the view state and validation data required to build the request stream correctly.

    private string ExtractViewsStateData(string responseString, string viewStateNameDelimiter)
    {
        string valueDelimiter = "value=\"";
        int viewStateNamePosition = responseString.IndexOf(viewStateNameDelimiter);
        int viewStateValuePosition = responseString.IndexOf(valueDelimiter, viewStateNamePosition);
        int viewStateStartPosition = viewStateValuePosition + valueDelimiter.Length;
        int viewStateEndPosition = responseString.IndexOf("\"", viewStateStartPosition);
        return HttpUtility.UrlEncodeUnicode(responseString.Substring(viewStateStartPosition, 
        viewStateEndPosition - viewStateStartPosition));
    }
    

To create the FormsAuthenticationSecurityTrimmer class for the project

  1. On the Project menu, click Add New Item.

  2. In the Add New Item dialog box, click Class, type FormsAuthenticationSecurityTrimmer.cs, and then click Add.

  3. Add the following using statements near the top of the code with the other namespace directives.

    using System.Net;
    using Microsoft.Office.Server.Search.Query;
    using Microsoft.Office.Server.Search.Administration;
    
  4. Specify that the FormsAuthenticationSecurityTrimmer class implements the ISecurityTrimmer interface in the class declaration, as follows.

    public class FormsAuthenticationSecurityTrimmer : ISecurityTrimmer
    
  5. Add the following code for the Initialize method declaration.

    public void Initialize(NameValueCollection trimmerProps, SearchContext searchCxt)
    {
    }
    
  6. Add the following code to implement the CheckAccess method.

    public BitArray CheckAccess(IList<String> crawlURLs, IDictionary<String, Object> sessionProperties)
    {
        BitArray retArray = new BitArray(crawlURLs.Count);
    
        for (int x = 0; x < crawlURLs.Count; x++)
        {
            bool haveAccess = this.DetermineAccess(crawlURLs[x]);
            retArray[x] = haveAccess;
        }
        return retArray;
    }
    

    The DetermineAccess method (see code in the next step) performs an access check for each crawl URL by using the forms authentication credentials that are entered by the current user. The method returns a Boolean value that indicates whether the user can access the content item for that crawl rule. The retArray variable stores the return value of the DetermineAccess method for each URL in the crawlURLs collection. If the user does not have access to a particular content item, the item is not included in the search results that are displayed for that user's query.

  7. Add the following code for the DetermineAccess method.

    public bool DetermineAccess(string url)
    {
        bool haveAccess = false;
        FormsAuthenticationCache currentUserCache = FormsAuthenticationCache.Current;
        if (currentUserCache != null)
        {
            FormsAuthenticationInfo authInfo = currentUserCache.AuthenticationInfo;
            if (authInfo != null && !authInfo.HasExpired)
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(newURL);
                request.AllowAutoRedirect = false;
                request.CookieContainer = authInfo.AuthenticationCookieContainer;
                try
                {
                    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                    {
                        if (response.StatusCode.Equals(HttpStatusCode.OK))
                        {
                            haveAccess = true;
                        }
                        else
                        {
                            haveAccess = false;
                        }
                    }
                }
                catch (Exception ex)
                {
                    haveAccess = false;
                }
            }
        }
        return haveAccess;
    }
    
  8. Optional. Add a method to the limit the number of crawl URLs that are checked for a search query. For a code sample that shows how to do this, see Step 3 (Optional): Specify a Configurable Limit on the Number of Crawl URLs Checked.

    Note

    This step, though optional, is recommended. For more details, see the "Performance Implications" section in Custom Security Trimming for Enterprise Search Results Overview.

Deploying the FormsAuthenticationSearchSample Project

Following are the prerequisites and steps you need to take to deploy the FromsAuthenticationSearchSample project.

Prerequisites for Deployment

Before you begin to deploy the FormsAuthenticationSearchSample project, ensure that you do the following:

  1. Install the hotfix described in the Knowledge Base article Sites That Require Forms-Based Authentication or Cookie-Based Authentication Are Not Crawled in SharePoint Server 2007.

  2. Add and configure a crawl rule for the forms authentication–protected site by using the steps described in the Knowledge Base article.

  3. Add and configure a content source for the forms authentication–protected site, and ensure that a full crawl of the content succeeds.

  4. Confirm that content from the forms authentication–protected sites are returned in search results.

Deployment Steps

Following are the basic steps to deploy the project:

  1. Give the FormsAuthenticationSearchSample assembly a strong name, and then build it.

  2. Install the FormsAuthenticationSearchSample assembly in the global assembly cache.

  3. Create a .webpart file for the FormsAuthenticationSearchSample Web Part.

  4. Register the FormsAuthenticationSearchSample Web Part as a safe control.

  5. Register the FormsAuthenticationSearchSample security trimmer

  6. Start a full crawl of the content source for the forms authentication site.

  7. Add the FormsAuthenticationSearchSample Web Part to the search results page.

To strong-name the assembly and build it

  1. With the FormsAuthenticationSearchSample project open in Visual Studio 2005, on the Project menu, click FormsAuthenticationSearchSample Properties.

  2. On the Signing tab, select the Sign the assembly check box.

  3. In the Choose a strong name key file list, click New.

  4. In the Create Strong Name Key dialog box, for Key file name, type a name for your key file, and then click OK.

    Note

    If you have an existing key file to use, click Browse instead of New for this step and locate the existing file.

  5. On the Build menu, click Build Solution.

For more information about this task, see How to: Sign an Assembly (Visual Studio). Alternatively, you can use Assembly Linker (AI.exe), which is available in the Microsoft .NET Framework SDK, to sign the assembly with a strong name as described in How to: Sign an Assembly with a Strong Name. For more information about strong-named assemblies, see Creating and Using Strong-Named Assemblies and Strong-Name Signing for Managed Applications.

After you sign your assembly with a strong name, you can install it in the global assembly cache.

To install the assembly in the global assembly cache

  1. Click Start, point to All Programs, point to Microsoft .NET Framework SDK v2.0, and then click SDK Command Prompt.

  2. At the SDK command prompt, type the following command.

    gacutil /if Path to Assembly\FormsAuthenticationSearchSample.dll 
    

    Replace Path to Assembly with the path to your DLL.

    Note

    When you use the /if switch, the tool overwrites the previous version of the assembly with the new version you are installing into the global assembly cache.

You also need to get the public key token value for the assembly.

To get the public key token value

  1. In Windows Explorer, locate FormsAuthenticationSearchSample.dll in the path Local_Drive:\WINDOWS\assembly\.

  2. Right-click the assembly, and then click Properties.

  3. In the Properties dialog box, on the General tab, select and copy the token.

Now you can create the .webpart file for the FormsAuthenticationSearchSample Web Part. This XML file describes the Web Part, and you use it to import the Web Part into the Web Part gallery.

To create the .webpart file

  1. Open a new file in a text editor such as Notepad, and add the following XML code to the file.

    <?xml version="1.0" encoding="utf-8"?>
    <webParts>
      <webPart xmlns="https://schemas.microsoft.com/WebPart/v3">
        <metaData>
          <type name="FormsAuthenticationSearchSample.FormsAuthenticationWebPart, 
          FormsAuthenticationSearchSample, 
          Version=1.0.0.0, Culture=neutral, 
          PublicKeyToken=PublicKeyTokenForYourWebPart" />
          <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
        </metaData>
        <data>
          <properties>
            <property name="Title" type="string">Forms Authentication Credentials Check</property>
          </properties>
        </data>
      </webPart>
    </webParts>
    
  2. Replace PublicKeyTokenForYourWebPartAssembly with the actual public key token value for your Web Part's assembly.

  3. Name the file FormsAuthenticationWebPart.webpart, and then save it.

To register the Web Part as a safe control

  1. Open the web.config file for the site you want to add the Web Part to. You can find this file in the root folder for the site.

  2. Add the following SafeControl element to the SafeControls section of the web.config file.

    <SafeControl 
      Assembly="FormsAuthenticationSearchSample, 
      Version=1.0.0.0, 
      Culture=neutral,
      PublicKeyToken=PublicKeyTokenForYourWebPart" 
      Namespace="FormsAuthenticationSearchSample" 
      TypeName="*" 
      Safe="True"
     />
    
  3. Save your changes, and then close the web.config file.

  4. Reset Internet Information Services (IIS). At a command prompt, type iisreset.

To register the security trimmer

  1. At a command prompt, type the following command.

    stsadm -o registersecuritytrimmer -ssp 
    SharedServicesName -id 1 -typeName 
    "FormsAuthenticationSearchSample.FormsAuthenticationSecurityTrimmer,
    FormsAuthenticationSearchSample, Version=1.0.0.0, 
    Culture=neutral, PublicKeyToken=token" -rulepath YourFormsAuthSiteCrawlRulePath
    
  2. In the command, replace SharedServicesName with the name of the SSP, replace token with the public key token for the FormsAuthenticationSearchSample.dll assembly, and replace YourFormsAuthSiteCrawlRulePath with the path for the crawl rule you set up when you installed the hotfix.

To re-create the content source

  1. Open SharePoint 3.0 Central Administration, and then navigate to the Application Management page.

  2. In the Office SharePoint Services Shared Services section of the Application Management page, click Create or Configure this Farm’s Shared Services.

  3. To open the administration site for the Shared Services Provider (SSP), click the name of the SSP for the search service.

  4. In the Search section, to open the Configure Search Settings page, click Search Settings.

  5. To open the Manage Content Sources page, click Content sources and crawl schedules.

  6. For the content source that represents the content affected by the crawl rule for the security trimmer, click the down arrow for Edit. On the menu, click Delete.

  7. To confirm deletion of the content source, click OK.

  8. Click Add Content Source, and then for Name, type the content source name.

  9. For Content Source Type, click Web Sites.

  10. For Start Addresses, type http://YourFormsBasedAuthSite/.

  11. Select Start full crawl of this content source, and then click OK to add the content source.

  12. Reset Internet Information Services (IIS). At a command prompt, type iisreset.

To add the Web Part to the search results page

  1. Open the Search Center Results page in your browser, click the Site Actions menu, and then click Edit Page.

  2. Click Add a Web Part in the Top Zone.

  3. In the Add Web Parts dialog box, click Advanced Web Part gallery and options.

  4. Click Browse, and then click Import.

  5. Click Browse, navigate to the location where you saved FormsAuthenticationWebPart.dwp, select it, and then click Open.

    You should now see the Forms Authentication Credentials Check in the dialog box. You can drag it onto the page to use it.

Conclusion

The pre-SP1 hotfix, described in the Knowledge Base article Sites That Require Forms-Based Authentication or Cookie-Based Authentication Are Not Crawled in SharePoint Server 2007, adds support to Enterprise Search for crawling remote SharePoint sites or other (not SharePoint-based) sites that are protected by forms authentication.

This enables content from these sites to be returned in search results. However for these results to be security trimmed, you must implement a custom security trimmer. The sample described in this article demonstrates implementing a custom security trimmer for this scenario.

Additional Resources

For more information, see these resources: