Partager via


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

  • Anonymous
    August 23, 2010
    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.
  • Anonymous
    August 24, 2010
    Can you repost your source code?  The link doesn't work for me.Thanks.
  • Anonymous
    August 24, 2010
    I've attached the source code in this post.
  • Anonymous
    September 03, 2010
    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?
  • Anonymous
    September 09, 2010
    The remember me option works for me. If I select this option, the login page will not be shown next time.
  • Anonymous
    September 17, 2010
    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! :-)
  • Anonymous
    September 19, 2010
    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.
  • Anonymous
    September 19, 2010
    @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?
  • Anonymous
    September 19, 2010
    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.
  • Anonymous
    September 20, 2010
    The project template I used is the Class Library.
  • Anonymous
    September 22, 2010
    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! :-)
  • Anonymous
    September 22, 2010
    I should correct myself -- I'm trying to replicate the functionality of the following pageC:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14TEMPLATEIDENTITYMODELLOGINNot 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...
  • Anonymous
    September 22, 2010
    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.
  • Anonymous
    October 19, 2010
    Thank you for sharing, helped me alot, when implementing a custom login page.
  • Anonymous
    October 28, 2010
    The comment has been removed
  • Anonymous
    December 29, 2010
    The comment has been removed
  • Anonymous
    February 22, 2011
    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.
  • Anonymous
    May 30, 2011
    HiWe 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 fineAny idea what causes this?Thanks
  • Anonymous
    August 23, 2011
    The comment has been removed
  • Anonymous
    November 18, 2011
    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.
  • Anonymous
    March 16, 2012
    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?
  • Anonymous
    March 28, 2012
    Hi Jake,for remember me to work, i ran the following commands on Powershell script.$sts = Get-SPSecurityTokenServiceConfig$sts.UseSessionCookies = $false$sts.Update()iisresetHope this helpsVasu
  • Anonymous
    June 17, 2013
    The above code not working in windows 8 IE 10 browser. please help me..forums.asp.net/.../1
  • Anonymous
    September 27, 2013
    Can't find the assembly Microsoft.Sharepoint.IdentityModel
  • Anonymous
    January 17, 2014
    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?
  • Anonymous
    March 04, 2015
    You shoud replace your IisSettings property for this. So you would be locked in Default Zoneinternal SPIisSettings IisSettings       {           get           {               SPWebApplication webApp = SPWebApplication.Lookup(new Uri(SPContext.Current.Web.Url));               return webApp.IisSettings[SPContext.Current.Site.Zone];           }       }