I have been working on a Forms Based Authentication (FBA) solution for an internet site and one of the requirements was the creation of a custom login page, one that will be somewhat more user friendly than the drop down page that SharePoint 2010 uses.
Most internet users will have no clue what to do with the "choose your claims provider" drop down so we wanted to show a page with a username and password box where most users will enter their credentials. These will be authenticated against the SQL database backed FBA, exactly what needs to happen for internet users. However we also want to show a small link that will automatically log in our internal users with Windows authentication.
This is not too difficult but I can see myself needing this again thus the blog post.
I started with following this blog post on
Creating a Custom Login Page for SharePoint 2010 by Kirk Evans, which worked great. Following his instructions gets you to the point of having a login page for your FBA users. All you need now is that link for your internal users.
First I modified the login control to make sure it goes against the correct Membership Provider, by adding the MembershipProvider attribute like:
<asp:Login ID="signInControl" FailureText="<%$Resources:wss,login_pageFailureText%>" MembershipProvider="FBAMembershipProvider"
runat="server" Width="100%" DisplayRememberMe="false" />
To be honest I'm not sure if that is needed, it just seemed logical to me.
Next I added a LinkButton right under the login control so I had markup like:
<asp:Login ID="signInControl" FailureText="<%$Resources:wss,login_pageFailureText%>" MembershipProvider="FBAMembershipProvider"
runat="server" Width="100%" DisplayRememberMe="false" />
<asp:LinkButton ID="hlInternalUsers" Text="Internal Login" runat="server" />
Lastly I added some simple code to the code beside file: (explanation below)
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
hlInternalUsers.Click += new EventHandler(hlInternalUsers_Click);
}
void hlInternalUsers_Click(object sender, EventArgs e)
{
if (null != SPContext.Current && null != SPContext.Current.Site)
{
SPIisSettings iisSettings = SPContext.Current.Site.WebApplication.IisSettings[SPUrlZone.Default];
if (null != iisSettings && iisSettings.UseWindowsClaimsAuthenticationProvider)
{
SPAuthenticationProvider provider = iisSettings.WindowsClaimsAuthenticationProvider;
RedirectToLoginPage(provider);
}
}
}
//borrowed from Microsoft.SharePoint.IdentityModel.LogonSelector
private void RedirectToLoginPage(SPAuthenticationProvider provider)
{
string components = HttpContext.Current.Request.Url.GetComponents(UriComponents.Query, UriFormat.SafeUnescaped);
string url = provider.AuthenticationRedirectionUrl.ToString();
if (provider is SPWindowsAuthenticationProvider)
{
components = EnsureUrlSkipsFormsAuthModuleRedirection(components, true);
}
SPUtility.Redirect(url, SPRedirectFlags.Default, this.Context, components);
}
//borrowed from Microsoft.SharePoint.Utilities.SPUtility
private string EnsureUrlSkipsFormsAuthModuleRedirection(string url, bool urlIsQueryStringOnly)
{
if (!url.Contains("ReturnUrl="))
{
if (urlIsQueryStringOnly)
{
url = url + (string.IsNullOrEmpty(url) ? "" : "&");
}
else
{
url = url + ((url.IndexOf('?') == -1) ? "?" : "&");
}
url = url + "ReturnUrl=";
}
return url;
}
The link button has an event handler attached to its Click event. The event handler makes sure there is an SPContext to work with and then gets an SPIisSettings object for the Default Zone of the current site. You can change the zone here if needed. If the SPIisSettings object is succesfully retrieved, we can check if Windows Authenticatoin is being used by this zone. If not, our link makes no sense! Next we get the Windows Authentication provider and call a helper method that will redirect the user to the right place.
The RedirectToLoginPage method is actually borrowed from the Microsoft.SharePoint.IdentityModel.LogonSelector class that is the control responsible for the drop down on the Out-of-box Claims logon page. As far as I can tell it plays with the urls so that the user is redirected to the correct login page (depending on the provider). Notice that this method uses yet another helper method, the EnsureUrlSkipsFormsAuthModuleRedirection method. This is a method borrowed from the SPUtility class, sadly it is internal so I just copied it out of reflector. You can use reflection if you want but I didn't want the performance hit. It just helps with some url magic.
So with a few lines of our own code and a few borrowed thanks to reflector, we have a login page that is very simple for our internet FBA users but still allows our internal users to log in with Windows Auth by clicking a simple link.