By Chris R. Chapman at May 08, 2010 02:47
Filed Under: claims based auth, sharepoint2010

While recently helping out my friends at Envision IT with their SharePoint 2010 FBA configuration, I came across an unsurprisingly frustrating aspect of using a custom ASP.NET form to handle the authentication.  From most of the sparse documentation that is available on this topic (re: custom FBA forms – there’s tons on OOTB FBA), you’d think that all that’s required is to provide an URL to your form in Central Administration and Bob is your father’s brother.

Centraladmin_customform
  Ta-da!  Ready to go home now!

Um, noooo.

The Bad News:  It Ain’t Gonna Work

It’d be great if things worked so easily as just slapping in an URL and the back-end wiring itself up automagically like it came out of Hogwarts – not so fast.  Say you have a basic ASP.NET form and you’ve configured it thus with standard <asp:Login> control:

<%@ Page Language="c#" AutoEventWireup="false" Inherits="System.Web.UI.Page" Trace="false" %>
<form id="fbaLoginForm" runat="server">
<asp:login id="signInControl" runat="server" displayrememberme="False"></asp:login>
<asp:checkbox id="chkRememberMe" runat="server" cssclass="RememberMe" text="Remember me and my language preference" tooltip="Tooltip goes here" visible="False" /> <br />
<span style="margin-left: 4px">
  <
a href="../ForgotPass/default.aspx" class="Label">
    <asp:label id="lblForgotPass" runat="server" text="Forgot Password?"></asp:label>
  </a>
</
span> </div> <script type="text/javascript" language="javascript">
// This Script focuses on the Username field (or the password field if the username was remembered)
if (document.getElementById("ctl00_ContentPlaceHolder1_Login1_UserName").value != "") {
document.getElementById(
"ctl00_ContentPlaceHolder1_Login1_Password").focus();
}
else {
document.getElementById(
"ctl00_ContentPlaceHolder1_Login1_UserName").focus();}
</script> </form>

Assuming that you’ve already set up the ASP.NET Membership SQL database, configured the Membership and Role providers, made the commensurate changes in the web app, Secure Token Service and Central Admin web.configs and gone into Central Administration to configure Forms Based authentication, you’d find that all your efforts would have been for nought:  This form would dutifully validate your credentials against the Membership database, but then unmercilessly given you a 403 HTTP error when the form attempted to redirect to /_layouts/Authenticate.aspx. (See my “How-To” FBA links on Delicious for reference if you’re lost in what I just said)

Why? 

Because a vanilla ASP.NET form has no idea how to create the required claims based authentication token to pass along to SharePoint’s doorman to say that you really are on “the list” and that you know this guy inside who can vouch for you.  This form inherits from System.Web.UI.Page – you are a nobody as far as Club SharePoint 2010 is concerned.  Move along.

The Good News:  You Can Make It Work

There is another way, but it is top-secret.  Well, at least it seems that way given the rigamarole I had to go through to put it together – there’s no API documentation on this, you see.  The solution is to go in-cognito:  In this case, to set your login form page to inherit from the same class that the out-of-the-box SharePoint FBA login form implementsMicrosoft.SharePoint.IdentityProvider.FormsSignInPage (this can be found in the /_forms sub folder of any SharePoint 2010 web application that has been set for FBA).

Ootb_login
  Our target:  The OOTB SharePoint 2010 FBA Form

Now through the magic of polymorphism, you too can access all the SharePoint FBA/Claims Auth token creation goodness out-of-the-box FBA forms get for “free”.  Please, hold your applause.

Ootb_fba_defaultaspx
  /_forms/default.aspx – You want to rip this code off

Using the default.aspx page as a guide, you can adapt your own custom login .aspx page and master page to implement the required <asp:Content> areas – you might want to keep them all or put the ones you don’t plan to use inside an <asp:Panel> control with the Visible property set to false.  Here’s what my revised login page default.aspx looks like – note the Assembly and Import directives and the Inherits property for the <% Page %> directive, and the <asp:Content> controls – these are required by the FormsSignInPage.

    1 <%@ Assembly Name="Microsoft.SharePoint.IdentityModel, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

    2 <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

    3 <%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

    4 <%@ Import Namespace="Microsoft.SharePoint.WebControls" %>

    5 <%@ Import Namespace="Microsoft.SharePoint" %>

    6 

    7 <%@ Page Language="c#" AutoEventWireup="false" MasterPageFile="DefaultGoodMasterPage.master"

    8     Inherits="Microsoft.SharePoint.IdentityModel.Pages.FormsSignInPage" Trace="false" %>

    9 

   10 <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"

   11     Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

   12 <%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities"

   13     Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

   14 

   15 <asp:Content ID="Content2" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">

   16     <SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" ID="ClaimsFormsPageTitle" />

   17 </asp:Content>

   18 <asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea"

   19     runat="server">

   20     <SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" ID="ClaimsFormsPageTitleInTitleArea" />

   21 </asp:Content>

   22 <asp:Content ContentPlaceHolderID="PlaceHolderSiteName" runat="server" />

   23 <asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server" />

   24 

   25 <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">

   26     <SharePoint:EncodedLiteral runat="server" EncodeMethod="HtmlEncode" ID="ClaimsFormsPageMessage" />

   27     <div style="text-align: left; margin-top: 10px;">

   28         <asp:Login ID="signInControl" runat="server">

   29         </asp:Login>

   30         <asp:CheckBox ID="chkRememberMe" runat="server" Text="Remember me"

   31             ToolTip="Tooltip goes here" Visible="False" />

   32         <br />

   33         <span style="margin-left: 4px"><a href="~/forgotmypassword/default.aspx">

   34             <asp:Label ID="lblForgotPass" runat="server" Text="Forgot Password?">

   35             </asp:Label>

   36         </a></span>

   37     </div>

   38     <script type="text/javascript" language="javascript">

   39         // This Script focuses on the Username field (or the password field if the username was remembered)

   40         if (document.getElementById("ctl00_ContentPlaceHolder1_Login1_UserName").value != "") {

   41             document.getElementById("ctl00_ContentPlaceHolder1_Login1_Password").focus();

   42         } else {

   43             document.getElementById("ctl00_ContentPlaceHolder1_Login1_UserName").focus();

   44         } 

   45     </script>

   46 </asp:Content>

   47 

Below is the master page code for my demo login form.  Note the <asp:Panel> control I’ve placed at line 32 that contains the <asp:ContentPlaceHolder> controls that are required by the FormsSignInPage class, but I don’t want to implement right now so I’ve hidden them.  This is an old trick I’ve borrowed from creating minimal master pages for SharePoint 2007 (nod to Heather Solomon;  what?  you don’t know who she is?  For shame…)

    1 <%@ Master Language="c#" %>

    2 

    3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    4 <html xmlns="http://www.w3.org/1999/xhtml" runat="server" id="html">

    5 <head id="Head1" runat="server">

    6     <title>Good SharePoint 2010 FBA Login</title>

    7     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

    8     <meta name="keywords" content="" />

    9     <!-- Head Content Ends here -->

   10     <asp:ContentPlaceHolder ID="ContentPlaceHolderHead1" runat="server">

   11     </asp:ContentPlaceHolder>

   12     <!-- Head Content Ends here -->

   13 </head>

   14 <body onload="if (typeof(body_onload) == 'function') window.setTimeout('body_onload();', 500);">

   15     <form id="form1" runat="server">

   16     <h1 style="color: Green">

   17         Good SharePoint 2010 FBA Login Form</h1>

   18     <div>

   19         This form will process and validate credentials for an FBA user and successfully

   20         redirect them to the SharePoint Site.

   21         <p>

   22             Specifically, it inherits from <b>Microsoft.SharePoint.IdentityModel.Pages.FormsSignInPage</b>

   23         </p>

   24     </div>

   25     <div>

   26         <!-- Page Content Starts here -->

   27         <asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">

   28         </asp:ContentPlaceHolder>

   29         <!-- Page Content Ends here -->

   30     </div>

   31     <!-- Required ContentPlaceHolders for parent page object -->

   32     <asp:Panel runat="server" Visible="false">

   33         <asp:ContentPlaceHolder ID="PlaceHolderPageTitle" runat="server">

   34         </asp:ContentPlaceHolder>

   35         <asp:ContentPlaceHolder ID="PlaceHolderPageTitleInTitleArea" runat="server">

   36         </asp:ContentPlaceHolder>

   37         <asp:ContentPlaceHolder ID="PlaceHolderSiteName" runat="server">

   38         </asp:ContentPlaceHolder>

   39         <asp:ContentPlaceHolder ID="PlaceHolderMain" runat="server">

   40         </asp:ContentPlaceHolder>

   41     </asp:Panel>

   42     </form>

   43 </body>

   44 </html>

   45 

Custom_login_good
  Behold:  This login form has actually been nominated for several design awards.

Using this technique, you can now adapt your pre-existing FBA login forms to conform with the claims based auth requirements for SharePoint 2010.  As far as I can tell, as of this writing there is no documentation around doing this – I had to use Reflector to determine what was going on inside the FormsSignInPage object as the API has no corresponding entries.

It’s a little more involved than just slapping down an ASP.NET page and setting the Sign In Form URL – but not that much more.

Please enjoy responsibly!

Comments

5/24/2010 9:21:33 PM #

David Nguyen

Very good article.  Great research.  I do have a question about deployment.  I can setup a module to deploy the custom master page, but how about the login page itself?  Is the default page generated from a template in the 14 hive?

Thank you for sharing.

David Nguyen |

5/25/2010 4:02:50 PM #

CRChapman

Hi David;

You could use a solution package (.wsp) to automagically deploy the custom form to your SharePoint site - that's relatively easy to do with Visual Studio 2010.  

Good question on what generates the default.aspx page that's in the _forms directory:  It would make sense that it lives in the 14 hive, however, I believe it isn't templated because it is always the same irrespective of the theme or other master page elements that are applied to the parent site.

CRChapman |

5/31/2010 6:40:52 AM #

oman

hello chris,

first of all, thanks for this tutorial...

i follow all the step that you mention...

when i open it in browser, i got an error..

it said " Microsoft.SharePoint.IdentityModel.Pages.FormsSignInPage not register as safe"

i know this related with web.config but i don have any idea which dll it used...

can you help me solve this problem???

thanks in advance...

sharepoint beginner,
oman

oman |

5/31/2010 3:46:11 PM #

CRChapman

@oman:  Usually when SharePoint complains that a control isn't registered as safe, it needs to be included in the web.config.  My advice is to first get the out-of-the-box FBA form working to confirm that your config settings are correct, then move to creating your own form using that default form (under /_forms/ in the web app virtual directory) as your template.

Hope this helps!

CRChapman |

Comments are closed

About Me

I am a Toronto-based software consultant specializing in SharePoint, .NET technologies and agile/iterative/lean software project management practices.

I am also a former Microsoft Consulting Services (MCS) Consultant with experience providing enterprise customers with subject matter expertise for planning and deploying SharePoint as well as .NET application development best practices.  I am MCAD certified (2006) and earned my Professional Scrum Master I certification in late September 2010, having previously earned my Certified Scrum Master certification in 2006. (What's the difference?)