Based on the foregoing discussion, making a copy of the simple.master
uses, copying login.aspx
into a subdirectory, and modifying web.config
to reference the file in the new directory is all relatively trivial. However, just copying the files has an important limitation: you cannot use compiled code in the page. Sure, you can include code inline in the page; however, this code isn't compiled and because it's uncompiled
it's a higher security risk than the same code in a compiled state.
In their book, Inside Windows SharePoint Services 3.0" (Microsoft Press 2007), Ted Pattison and Daniel Larson discuss a technique in which you inherit your pages from LayoutsPageBase to use them in the _layouts directory. This great technique comes with one small problem: it won't work for the login page. LayoutsPageBase is designed to make sure the user is authorized to see the content. However, because you haven't authenticated who the user is yet, that authorization won't work. Luckily, LayoutsPageBase derives from UnsecuredLayoutsPageBase, which exposes a protected Boolean property, AllowAnonymousAccess. You can override this Boolean property to return true to indicate that anonymous users should have access to the page.
Figure 1. Principal User Name: The authentication provider offers out-of-the-box support.
Figure 2. The Real Email Address: Any domain can be set for the E-mail property, even those not associated with active directory.
In addition to knowing what class to inherit from, you need to know how to create the compiled code. You can create it by producing a separate class library to contain the class that the page will inherit. In other words, the page won’t inherit directly from UnsecuredLayoutsPageBase; it will derive from a class that you create, which derives from UnsecuredLayoutsPageBase. The aspx page will in turn inherit from the class that you've created.
The special bit of magic that makes this technique really slick is that you can create protected properties in your class for each of the user interface controls that appear on the login page. When ASP.NET compiles the page it will connect the controls declared in your custom base class with the controls in the ASPX file, provided that the ID of the control in the ASPX matches the name of the field in your custom class. In other words, you don't have to worry about how the fields get populated; ASP.NET will handle this work for you.
Assemble the Pieces
Now that you have an understanding of the basic components of how the solution fits together it's time to create your own solution that leverages these components. For the purposes of the example, you're going to create a login page that allows users to log in through their email addresses rather than through their usernames. It has become a common practice in the industry to have users log in with their email addresses rather than a username. This technique seems to be easier for users to remember, and email addresses are generally unique.
It might seem like changing to a login through an email address would be easy. You change the property mapped to username in the configuration of the System.Web.Security.ActiveDirectoryMembershipProvider, andpoofyou have your solution. However, while it's true that you would be able to make this change work for all internal users by using UserPrincipalName, external users wouldn't be able to log in using their email addresses because they would be able to log in only with the email address that corresponds to their account in active directory. Generally, external users are not going to think of this requirement.
Figure 1 shows the principal user name on the Account tab. The authentication provider will support this address out of the box. Figure 2 shows the E-mail property on the General tab that can be set to any domain, whether or not it's related to the active directory domain.
There is, however, a way to make this change work. The ActiveDirectoryMembershipProvider supports a standard MembershipProvider method called FindUsersByEmail(). If you add the attribute enableSearchMethods="true" to the configuration of the provider, you can get users' usernames and pass them into the ValidateUser() method to validate who they are. This approach allows users to log in with their email addresses, as specified on the General tab in active directory.