Creating a Custom Login Page for FBA in SharePoint 2010


The default login page of FBA in SharePoint 2010 is very simple. It only provides a Login control with the user name, password and remember me option. We usually have to create a custom login page to accommodate other security requirements, for example, adding a verification/secure code option on the page, other performing a double secure check etc.

As FBA in SharePoint 2010 has been changed to be based on Claims authentication, creating a custom login page for it is a quite different from MOSS 2007. As Steve Peschka mentioned, the FormsAuthentication class is not used anymore. Now we need to deal with the SharePoint STS and Claims when authenticating users. Fortunately, SharePoint 2010 provides us interfaces to make the things simple. In this article, I am going to show you how we can create a custom login page with the build-in master page and look-and-feel.

Please note that I am not sure whether the way I mentioned here is official supported by Microsoft Support. So you’ll have to take your own risk when you use it in your production.

Creating a Custom Login Page with the Build-in Look-and-Feel

If we take a look at the login page of a FBA enabled site, we will find that it is actually the default page in the virtual folder _forms. The design in this way just make things easier because every web app has its own login page. The modification on one web app will not affect others.

To add a Secure code option on the page, I add the following code in the Login control.

 <asp:login id="loginControl" 
    FailureText="<%$Resources:wss,login_pageFailureText%>" 
    runat="server" width="100%" OnLoggingIn="signInControl_LoggingIn" 
    OnAuthenticate="signInControl_Authenticate">
    <layouttemplate>
        <asp:label id="FailureText" class="ms-error" runat="server"/>
        <table width="100%">
        <tr>
            <td nowrap="nowrap">
                <SharePoint:EncodedLiteral runat="server" 
                    text="<%$Resources:wss,login_pageUserName%>" 
                    EncodeMethod='HtmlEncode'/>
            </td>
            <td width="100%">
                <asp:textbox id="UserName" 
                    autocomplete="off" 
                    runat="server" 
                    class="ms-inputuserfield" width="99%" />
            </td>
        </tr>
        <tr>
            <td nowrap="nowrap">
                <SharePoint:EncodedLiteral runat="server" 
                    text="<%$Resources:wss,login_pagePassword%>" 
                    EncodeMethod='HtmlEncode'/>
            </td>
            <td width="100%">
                <asp:textbox id="password" TextMode="Password" 
                    autocomplete="off" runat="server" 
                    class="ms-inputuserfield" width="99%"/>
            </td>
        </tr>
        <tr>
            <td nowrap="nowrap">
                <SharePoint:EncodedLiteral runat="server" 
                    text="Secure Code:" EncodeMethod='HtmlEncode'/>
            </td>
            <td width="100%">
                <asp:textbox id="secureCode" autocomplete="off" 
                    runat="server" class="ms-inputuserfield" Width="85%" />
                <SharePoint:EncodedLiteral ID="secureCodeLit" 
                    runat="server" Text="1234" EncodeMethod="HtmlEncode" />
            </td>
        </tr>
        <tr>
            <td colspan="2" align="right">
                <asp:button id="login" commandname="Login" 
                    text="<%$Resources:wss,login_pagetitle%>" runat="server" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <asp:checkbox id="RememberMe" 
                    text="<%$SPHtmlEncodedResources:wss,login_pageRememberMe%>" 
                    runat="server" />
            </td>
        </tr>
        </table>
    </layouttemplate>
 </asp:login>

What I want to do is only when users input the correct user name and password with the Secure Code, that is 1234 in this demo, they will allowed to access the site. When browsing the site, its login page looks like the following:

image

Of course the Secure Code does not function now because the build-in sign in page does not know how to handle it. I have to create my own module to proceed the secure code as well as the sign in logic.

Creating a Code Behind Class to Handle the Request of the Sign-in Page

Now its time to write some code to handle the sign-in request. In the project, add a new class and name it to, for example, FormsSignInPage. At this point, we have to reference to some necessary assemblies. This is a page for SharePoint site, so the first assembly we must reference to is Microsoft.SharePoint.dll. Since we are dealing with Claims, System.IdentityModel.dll and Microsoft.IdentityModel.dll are necessary as well. SharePoint has its own module to deal with Claims too. It is Microsoft.SharePoint.IdentityModel.dll. To reference to it, we have to browse to its location and add it. The assemblies referenced in my project look like the following:

image

The class, FormsSignInPage, can derive from the Page class of the Asp.Net. However, if you check the build-in sign-in page, it derives from IdentityModelSignInPageBase which is under Microsoft.SharePoint.IdentityModel.Pages namespace. It provides some benefits for signing in with claims. So I just made my FormsSignInPage derive from this base class too.

When users click Sign In button, I handle two events of the Login control, LoggingIn and Authenticate. In LoggingIn event, I check whether the correct Secure Code has been input. If not, I just cancel the sign-in. In Authenticate events, the actual authentication action is performed.

protected void signInControl_LoggingIn(object sender, LoginCancelEventArgs e)
{
    LoginControl login = sender as LoginControl;
    login.UserName = login.UserName.Trim();
    if (string.IsNullOrEmpty(login.UserName))
    {
        ClaimsFormsPageMessage.Text = "The server could not sign you in. The user name cannot be empty.";
        e.Cancel = true;
    }
    if (string.IsNullOrEmpty(secureCode.Text) || 
        !string.Equals(secureCode.Text.ToLower(), secureCodeLit.Text.ToLower()))
    {
        ClaimsFormsPageMessage.Text = "The server could not sign you in. Please input correct secure code.";
        e.Cancel = true;
    }
}
private void EstablishSessionWithToken(SecurityToken securityToken)
{
    if (null == securityToken)
    {
        throw new ArgumentNullException("securityToken");
    }
    SPFederationAuthenticationModule fam = SPFederationAuthenticationModule.Current;
    if (null == fam)
    {
        throw new ArgumentException(null, "FederationAuthenticationModule");
    }

    fam.SetPrincipalAndWriteSessionToken(securityToken);
}

protected void signInControl_Authenticate(object sender, AuthenticateEventArgs e)
{
    SecurityToken token = null;
    LoginControl formsLoginControl = sender as LoginControl;

    if (null != (token = GetSecurityToken(formsLoginControl)))
    {
        EstablishSessionWithToken(token);
        e.Authenticated = true;
        base.RedirectToSuccessUrl();
    }
}
private SPIisSettings IisSettings
{
    get
    {
                
        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(SPContext.Current.Web.Url));
        SPIisSettings settings = webApp.IisSettings[SPUrlZone.Default];
        return settings;
    }
}

private SecurityToken GetSecurityToken(LoginControl formsLoginControl)
{
    SecurityToken token = null;
    SPIisSettings iisSettings = IisSettings;
    Uri appliesTo = base.AppliesTo;

    if (string.IsNullOrEmpty(formsLoginControl.UserName) ||
        string.IsNullOrEmpty(formsLoginControl.Password))
        return null;

    SPFormsAuthenticationProvider authProvider = iisSettings.FormsClaimsAuthenticationProvider;
    token = SPSecurityContext.SecurityTokenForFormsAuthentication(
        appliesTo,
        authProvider.MembershipProvider,
        authProvider.RoleProvider,
        formsLoginControl.UserName,
        formsLoginControl.Password);

    return token;
}

Key part of our page is how to perform sign-in action. SharePoint provide us a function called SecurityTokenForFormsAuthentication which is used to perform Forms authentication. The code I am using is below. I get the membership provider and roleship provider from the SPIisSettings.

SPFormsAuthenticationProvider authProvider = iisSettings.FormsClaimsAuthenticationProvider;
token = SPSecurityContext.SecurityTokenForFormsAuthentication(
    appliesTo,
    authProvider.MembershipProvider,
    authProvider.RoleProvider,
    formsLoginControl.UserName,
    formsLoginControl.Password);

Making a Connection Between Default.aspx and FormsSignInPage

The final step is to make a connection between default.aspx and FormsSignInPage. To make it, we just need to modify the <%@ Page %> of the default.aspx like this:

<%@ Page Language="C#" AutoEventWireup="true" 
    Inherits="Morpheus.Demo.Pages.FormsSignInPage,FormsSignInPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=72d2bbe72853b8eb" 
    MasterPageFile="~/_layouts/simple.master" %> 

Then we can deploy the assembly of our code to GAC and the default.aspx to _forms folder of our web app. When signing in with our custom page, without inputting Secure Code correctly, users will get an error like following.

image

I’ve shared the source code of my project here. And the Chinese version of this article can be found here.

FormsSignInPage.zip

Comments (26)

  1. CBB_II says:

    Chun Liu, sorry for the cross post, but I posted some questions on your blog: "How to make use of a custom IP-STS with SharePoint 2010? (Part 2)"  If you get a chance please take a look and share your thoughts. Thanks in advance.

  2. Jabahooie says:

    Can you repost your source code?  The link doesn't work for me.

    Thanks.

  3. Chun Liu says:

    I've attached the source code in this post.

  4. Great, but I need the remember me woks says:

    Hi, thanks for write this article, but I have a problem, all post i read show the remember me option, but when i implement the example and test the remember me, not work, and allways remember.

    What happend with remember me option and login control + clam autentication + sharepoint 2010?

    Can you give me light?

  5. Chun Liu says:

    The remember me option works for me. If I select this option, the login page will not be shown next time.

  6. Peacock says:

    Hi, as other have said, thanks for this article. It's helped me alot. I was wondering if you could possibly answer a question I have. I'm planning on running mixxed authentication (windows and forms based) adn would like to add to your solution, however, I'm having troubles figuring out how to issue the SecurityToken when authenticating against a WindowsClaimsAuthenticationProvider.

    You provide the way to get a SecurityToken when authenticating a FormsClaimsAuthenticationProvider. Could you shed light on how to get one when using a WindowsClaimsAuthenticationProvider please? Thank you if you can! 🙂

  7. SC Vinod says:

    Hi Chin Liu,

    Thank for the gr8 article. I have few issues. When I create a custom Login page using Application page in Sharepoint I get "403 FORBIDDEN" error.

  8. Chun Liu says:

    @Peacock, Windows Authentication under Claims mode is not handled by SPWindowsClaimsAuthenticationProvider. Rather, it is handled by SPWindowsClaimsAuthenticationHttpModule. So if you want to customize the process of Windows Authentication, just verify whether the Identity of the current user is a claims identity or not. I am going to create another demo if I have time.

    @SC Vinod, I am not quite clear about what you have done. Have you tried the same way I mentioned in this article?

  9. Project Template says:

    Hi Chin Liu,

    Thanks for your reply. I was following a different method. But Now I just copied the Default.aspx page from your project and put tht in _forms folder and the dll in to my GAC. It's working well now. Can you tell me what is your project template?? Is it an ASP.NET Website Project or?? Because I need to put a link in the Login Control which when clicked must navigate to another .ASPX page where the user can register for logging in. Thank you.

  10. Chun Liu says:

    The project template I used is the Class Library.

  11. Peacock says:

    Hi Chun Liu,

    Thanks for the feedback.I've looked at your most recent article blogs.msdn.com/…/creating-a-custom-login-page-for-windows-authentication.aspx which I believe is what you created based on your understanding of my request. However, it's not quite what i had in mind.

    From my understanding, your example you override the  /_windows/default.aspx page (where windows authentication is performed). However, this is not what I want.

    I am currently using a custom Login page with claims authentication. I basically want to authenticate against two membership providers where the user selects which authentication provider to use.

    Example: Sharepoint site configured to use claims authentication with a custom sign in page located as "~/_layouts/CustomLogin/MyLogin.aspx"

    On MyLogin.aspx, there are two hyperlinks. If the user selects/clicks the first hyperlink, they are authenticated automatically with windows authentication (or ideally they have to enter their domain credentials again). If the user selects/clicks the second hyperlink, they are authenticated with a custom provider.

    I actually have the second scenario (custom provider authentication) working successfully. It's the first scenario that I can't get working. Another way to look at this example is me wanting to customize the layout of how the default login.aspx page works when using claims authentication. By default, sharepoint provides the login.aspx page with a dropdownlist providing which authentication provider to use. I am basically replicating this, but using hyperlinks instead of the dropdownlist.

    Hope this makes sense to you. Thanks again! 🙂

  12. Peacock says:

    I should correct myself — I'm trying to replicate the functionality of the following page

    C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14TEMPLATEIDENTITYMODELLOGIN

    Not what I had previously mentioned as the default login.aspx behaviour.

    I'd like to figure out the way to replicate the logic of the 'windows authentication' done when the user selects this item from the <SharepointIdentity:LogonSelector ID="ClaimsLogonSelector" runat="server" /> control on this page. Hope this helps…

  13. Chun Liu says:

    Then the problem is simple. When the user clicks the first link, redirect them to /_windows/default.aspx with the corresponding query strings in the url. As I mentioned in another post, the Windows Auth is actually handled by a HttpModule. When you request the page /_windows/default.aspx, that HttpModule will authenticate the user.

  14. Lars U says:

    Thank you for sharing, helped me alot, when implementing a custom login page.

  15. prasad says:

    hi,

    i have  gone thru your code and trying to implement the same usinga  custom membership provider and custom role provider  classso i have deployed  the  dlls in the gac, 3 web,config's 9centrala dmin, my web appln, sts web config]

    but when  i debug7 after e.authenticate d=true line, i am gettiong access  denied erro.

    can you tell me  why this is happeneing? in the custom membership provider  class  , i am hard coding th  username and password[ in the validateuser(usr , pwd) method- [override  bool].

    somehow the sharepoint is not able to recognize this user.

    pls help

  16. Hi Chun Liu,

    Your article is very nice, actually I have some different scenario. I have some custom code that i want to execute on page_load of the login page and I dont wants that user should enter the user name and pwd and click on sign in button. I want to redirect user to website in page load it self.

    Can you give me your valuable thoughts on this. What i need to do for this.

    Thanks in Advance,

    Sachin

  17. Tengiz Tutisani says:

    Hi.

    I need to have a login page on other asp.net website. How can I issue the auth ticket for sharepoint so that it works with it and does not ask for login again?

    Any thoughts?

    Thanks.

  18. nuclear says:

    Hi

    We are putting the Login control on the Home page of the site and then switch to Forms based authentication only, we get 403 Forbidden error.

    If we have Windows Authentication as well, we do get the drop-down question and we're directed back to the Home screen and then the Login works fine

    Any idea what causes this?

    Thanks

  19. Guru says:

    Hi,

    I have the logic of source code attached in this blog. When I click on Sign-Out link in share point, I am getting an error in the browser "500 INTERNAL SERVER ERROR". When I digged into the ULS logs, I got below exception, can you please help me how to resolve this issue.

    System.ArgumentException: Exception of type 'System.ArgumentException' was thrown.  Parameter name: encodedValue    at Microsoft.SharePoint.Administration.Claims.SPClaimEncodingManager.DecodeClaimFromFormsSuffix(String encodedValue)     at Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager.GetProviderUserKey(String encodedSuffix)     at Microsoft.SharePoint.ApplicationRuntime.SPHeaderManager.AddIsapiHeaders(HttpContext context, String encodedUrl, NameValueCollection headers)     at Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.PreRequestExecuteAppHandler(Object oSender, EventArgs ea)     at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&… 5447c485-39d9-442e-8de0-836ac3dbb9c7

  20. Xprt says:

    Hi,

    I am facing an issue after implementing custom FBA login page. It works all fine but when I click on Sign-Out link in share point, I am getting an error in the browser "500 INTERNAL SERVER ERROR".  

    Can you PLEASE HELP me how to resolve this issue.

  21. Jake says:

    Remember Me option does not work….it suppose to remember you for 50 years until you sign out, but when you look at the FedAuth cookie, it shows what the forms time out limit is….Any ideas?

  22. Vasu says:

    Hi Jake,

    for remember me to work, i ran the following commands on Powershell script.

    $sts = Get-SPSecurityTokenServiceConfig

    $sts.UseSessionCookies = $false

    $sts.Update()

    iisreset

    Hope this helps

    Vasu

  23. Vipin V Nair says:

    The above code not working in windows 8 IE 10 browser. please help me..

    forums.asp.net/…/1

  24. Rodrigo says:

    Can't find the assembly Microsoft.Sharepoint.IdentityModel

  25. Poomani Sankaran says:

    Any one can give me reply?

    The above sample is working well for me. But as per my scenario. i need to achieve without "Password".

    How can i get it?

  26. Ricardo says:

    You shoud replace your IisSettings property for this. So you would be locked in Default Zone

    internal SPIisSettings IisSettings

           {

               get

               {

                   SPWebApplication webApp = SPWebApplication.Lookup(new Uri(SPContext.Current.Web.Url));

                   return webApp.IisSettings[SPContext.Current.Site.Zone];

               }

           }