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.




13 comments:
thank you. Saved my life.
I'm not sure how this will work? Coming to an anonymous login the SPContext.Current will always be null. How do you get around this?
Thank you
@Travis, the SPContext.Current is not null for anonymous users. Even if the user is not authenticated,there is information about the current SPSite and SPWeb that they are browsing for instance. I can assure you this code works, it is currently being used by a customer of mine.
@Joe, I'm wondering if because I'm not inheriting from a masterpage if that's why I'm always NULL? I ended up using just these 3 lines of your code and got where I needed:
string components = HttpContext.Current.Request.Url.GetComponents(UriComponents.Query, UriFormat.SafeUnescaped);
components = EnsureUrlSkipsFormsAuthModuleRedirection(components, true);
SPUtility.Redirect("/_windows/default.aspx", SPRedirectFlags.Default, this.Context, components);
Thank you,
Travis
@Joe, Sorry, looks like my Windows 7 environment is the culprit. I packaged up my solution and deployed to a windows server test environment and it works. Sorry to doubt your work :)
Thank you,
Travis Trout
I added "_forms/default.aspx" as the url for the custom login page. It nagivates right to the fba login page.
Nice one Joe! Took me too much time to figure it our, but thanks to your post I got it working fine now!
Thank you very much, Joe. You saved my day. Greetings from Cologne, Alex
Is there anyway to use the signInControl.UserName and signInControl.Password to windows validation and login ?
Thanks
@Hernan, I don't think so since windows authentication is integrated in the browser but I could be wrong.
Hi Joe,
Just wanted to say thanks...it's not often you find such elegant solutions on blogs, but after reading your article - five minutes later it's working for me
Thanks
Tobias
Does anyone know why my SPContext.Current is always null?
Thanks in advance
My appoligies.
Great Post.
I had to change the url in the custom login page to "~/sites/site/_layouts/login/login.aspx"
Post a Comment