Pages

Thursday, May 26, 2011

SharePoint Custom Logon Page

Recently a client came to me and said they didn’t like how the logon page looked for Claims Based Authentication. And frankly – who could blame them? This isn’t the most attractive UI I’ve ever seen.

They wanted a solution that met the following requirements:

Display a list of links for each authentication mechanism

A page that could be customized for new web applications

Didn’t require contacting me for new pages

For the solution, I settled on a basic ASPX page that could be copied and pasted as needed, ensuring it always used the same base codebehind file. (All the code listed below is in this codebehind file.) The ASPX page could then be easily customized as needed, with the only “tricky” part being the addition of new links for the authentication mechanisms. The links take the following format:

<asp:LinkButtonText="Logon Using Windows Authentication"

runat="server" ID="btnAdfs"OnCommand="logonButton_Command"

CommandArgument="Windows Authentication"/>

Whenever a new authentication mechanism is needed, simply add a new link, with the appropriate Text for the user and the CommandArgument set to the DisplayName of the authentication mechanism. To find the display name, you can execute the Get-SPAuthenticationProvider PowerShell command.

Figuring out how to configure the web application to use the new authentication page proved relatively easy. I fired up my Google Machine[1] and found this blog post by Kirk Evans in short order. All that’s needed is a new page in Layouts and to update the web application to point at the page.

The hard part for the development was figuring out how to mimic the behavior of the built-in logon page. Unfortunately, the control that’s used (LogonSelector), does a lot of its magic internally and doesn’t expose those methods publicly. With a little help from ildasm and a lot of help from Joe Capka I was able to put together the logic needed. The event handler for the button looks a little like this:

protectedvoid logonButton_Command(Object sender, CommandEventArgs e)

{

SPAuthenticationProvider provider =

GetAuthenticationProvider(e.CommandArgument.ToString());

RedirectToLoginPage(provider);

}

The two methods GetAuthenticationProvider and RedirectToLoginPage are built into the LogonSelector. The first one I was able to figure out on my own, the next two I needed Joe[2] for.

To find the authentication provider, we loop through all the ones available for the web application and return the right one back. This is where the name in the CommandArgument needs to match the DisplayName for the provider.

privateSPAuthenticationProvider

GetAuthenticationProvider(string providerName)

{

SPIisSettings iisSettings =

SPContext.Current.Site.WebApplication.IisSettings[SPUrlZone.Default];

foreach (SPAuthenticationProvider currentProvider in

iisSettings.ClaimsAuthenticationProviders)

{

if(currentProvider.DisplayName.ToLower() ==

providerName.ToLower()) {

return currentProvider;

}

}

returnnull;

}

I know I’ve got a “ToLower” call in there. I’m OK with that. ;-)

The RedirectToLoginPage needs a little more work, as there may be parameters in the URL that need to be kept. The call to EnsureUrlSkipsFormsAuthModuleRedirection takes care of that part.

privatevoid RedirectToLoginPage(SPAuthenticationProvider provider)

{

string components = HttpContext.Current.Request.Url.GetComponent

(UriComponents.Query, UriFormat.SafeUnescaped);

string url = provider.AuthenticationRedirectionUrl.ToString();

if (provider isSPWindowsAuthenticationProvider)

{

components =

EnsureUrlSkipsFormsAuthModuleRedirection(components, true);

}

SPUtility.Redirect(url, SPRedirectFlags.Default,

this.Context, components);

}

privatestring 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 goals have now been met. If you need a new custom logon page, simply copy/paste the ASPX file. Add the links as needed, ensuring that the CommandArgument is set to the DisplayName of the authentication provider. Point the web application a the custom page, and you’re done!