Partager via


Creating a Custom Login Page for Windows Authentication

A month ago, I wrote an article talking about how to create a custom login page for FBA in SharePoint 2010. Since then, I got some enquiries asking whether it is possible to do the same thing for Windows Authentication. So I did some research and created another demo, and here comes this article. Again, I don’t know whether this is a supported way. So you’ll have to take your own risk when you use it in your production.

Preparation

In this demo, I am dealing with the following scenario. Say in my org every user has his/her own Personal Identity Number (PIN). When they login to the SharePoint site, in addition to the user credential of AD, I also want them to use their PIN as a double check. I don’t want them input anything except PIN. Here comes how I do it by customizing the login page of the Windows Authentication.

In a Claims-mode web app, the Windows Authentication is handled by /_windows/default.aspx. If we check the path of the virtual folder /_windows, it points to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\IDENTITYMODEL\WINDOWS. Obviously, modifying the files in this folder directly is not a good idea. So what I did first is to create another folder, copy all files (default.aspx and web.config) in the above folder to the folder I created, and modify the path of the virtual folder to point to the new folder. I tested the site with the new folder. Everything works fine.

Creating a Customer Login Page

All the same as what I did for the login page for FBA, I created a page using simple.master. Since there is no need for users to input user name and password, a login control is not necessary. Instead, I just put a textbox, a button and several labels on the page. The layout looks like the following.

image

Creating a Code Behind Class to Handle the Request

I then created a class named as WindowsSignInPage and make it derive from IdentityModelSignInPageBase just like the forms sign in page. Again, I added all necessary assembly references.

Now it is time to write the logic to handle the real authentication. Different from the Forms authentication, in Windows authentication in Claims mode users are not authenticated by /_windows/default.aspx directly. Instead, they are authenticated by an HttpModule, SPWindowsClaimsAuthenticationHttpModule. When a user browse the web site and reaches /_windows/default.aspx, he/she has already passed the Windows authentication performed by that HttpModule. So to force users to input their PIN on the page, we must sign them out first. The following is the code I added in Page_Load.

 IClaimsIdentity claimsIdentity =
    HttpContext.Current.User.Identity as IClaimsIdentity;
if (claimsIdentity != null)
{
    CurrentUserLit.Text = claimsIdentity.GetDisplayName();
}
else
{
    CurrentUserLit.Text = HttpContext.Current.User.Identity.Name;
}

SecurityToken token = SPSecurityContext.SecurityTokenForContext(Context.Request.Url);
HttpCookie signInCookie = Context.Request.Cookies["Morpheus_WindowsSignedIn"];
if (signInCookie != null && signInCookie.Value.Equals("true"))
{
    RedirectToSuccessUrl();
}
else
{
    RemoveCookiesAndSignOut();
}

Since the user has passed the authentication of the HttpModule, the identity we get has been an IClaimsIdentity already. Here I check my custom cookie first. If the cookie exists, means the user has input the correct PIN. Otherwise, the user is signed out so he/she must input the PIN first.

The code of RemoveCookieAndSignOut is as below. If you examine the code of Signout.aspx with Reflector, you will see that I am using almost the same logic with it.

 private void RemoveCookiesAndSignOut()
{
    // Clear session state. 
    if (Context.Session != null)
    {
        Context.Session.Clear();
    }

    string cookieValue = string.Empty;
    if (Context.Request.Browser["supportsEmptyStringInCookieValue"] == "false")
        cookieValue = "NoCookie";

    // Clear my own cookie.
    HttpCookie cookieWinSignIn = Context.Request.Cookies["Morpheus_WindowsSignedIn"];
    if (cookieWinSignIn != null)
    {
        cookieWinSignIn.Value = cookieValue;
        Context.Response.Cookies.Remove("Morpheus_WindowsSignedIn");
        Context.Response.Cookies.Add(cookieWinSignIn);
    }

    // Remove cookies for authentication. 
    HttpCookie cookieSession = Context.Request.Cookies["WSS_KeepSessionAuthenticated"];
    if (cookieSession != null)
    {
        cookieSession.Value = cookieValue;
        Context.Response.Cookies.Remove("WSS_KeepSessionAuthenticated");
        Context.Response.Cookies.Add(cookieSession);
    }

    HttpCookie cookiePersist = Context.Request.Cookies["MSOWebPartPage_AnonymousAccessCookie"];
    if (cookiePersist != null)
    {
        cookiePersist.Value = cookieValue;
        cookiePersist.Expires = new DateTime(1970, 1, 1);
        Context.Response.Cookies.Remove("MSOWebPartPage_AnonymousAccessCookie");
        Context.Response.Cookies.Add(cookiePersist);
    }

    // Sign out.
    Microsoft.IdentityModel.Web.FederatedAuthentication.SessionAuthenticationModule.SignOut();
}

The final part is very simple. When the user clicks the submit button, I check his/her PIN first. If the PIN is correct, I set the cookie and redirect him/her to this page again so that the HttpModule authenticates the user again.

 protected void LoginBtn_Click(object sender, EventArgs e)
{
    // Check whether the PIN is correct.
    if (!string.Equals(pinCode.Text.ToLower(), pinLit.Text.ToLower()))
    {
        ClaimsFormsPageMessage.Text = "The PIN is incorrect.";
        return;
    }

    // Set cookie and redirect to the sign in page
    // so the SPWindowsClaimsAuthenticationHttpModule can sign us in again.
    HttpCookie cookie = Context.Request.Cookies["Morpheus_WindowsSignedIn"];
    if (cookie == null)
    {
        cookie = new HttpCookie("Morpheus_WindowsSignedIn");
    }
    else
    {
        Context.Response.Cookies.Remove("Morpheus_WindowsSignedIn");
    }
    cookie.Value = "true";
    cookie.Expires = DateTime.Now.AddMinutes(1); // 1 minute for testing purpose.
    Context.Response.Cookies.Add(cookie);
    SPUtility.Redirect("/_windows/default.aspx", SPRedirectFlags.Default, Context);
}

Deploying and Testing

To deploy the page, connect the default.aspx and the assembly by adding a <%@ Page %> indicator, put default.aspx in /_windows and the assembly in GAC, and reset the app pool. Then browse the site to see the result.

The attachment is the source code of the demo.

WindowsSignInPage.zip

Comments

  • Anonymous
    January 17, 2011
    Hellonice article, but how to program custom login page with login and password for windows users ?thanks
  • Anonymous
    January 24, 2011
    The title of this article is a little misleading since this login form doesn't actually replace windows authentication but rather allows you to require a pin confirmation after the windows authentication has already successfully occurred.  Not that the former is even possible, at least as far as I know.
  • Anonymous
    February 20, 2011
    Hello Is it possible to have a custom login page for Claims enabled windows authentication to just have user id and password
  • Anonymous
    February 20, 2011
    Hi I am new to Sharepoint as you stated in article how do I perform the step  "modify the path of the virtual folder to point to the new folder". Thanks in advance
  • Anonymous
    March 25, 2012
    The comment has been removed
  • Anonymous
    February 19, 2013
    who needs to use PIN???? this is funny :D
  • Anonymous
    February 24, 2013
    Please give step by step implementation.
  • Anonymous
    April 15, 2013
    What's funny is people posting comments asking for something that is linked in the first sentence.  If you want username and password.  Read the first sentence, click the link, remove the functionality for pin.  Success.
  • Anonymous
    October 07, 2014
    The comment has been removed
  • Anonymous
    November 10, 2014
    We have a requirement for creating a custom login page for SharePoint 2010 application which is in Windows authentication. Once we try to access the application we are getting an windows authentication popup. Instead of this popup we need to get a custom login page to enter user name and password.Could you please help me on the above requirement
  • Anonymous
    March 24, 2015
    The comment has been removed
  • Anonymous
    June 08, 2015
    The comment has been removed
  • Anonymous
    June 27, 2015
    This code is misleading, its not windows auth but its just sending the user to the windows login prompt, which does the auth by agin giving you the normal windows login prompt