Bypassing Multiple-Authentication Providers in SharePoint 2013

Intro

I haven't researched to see if this topic has been heavily documented but feel like putting my 2 cents of input so hope this helps you. One of Microsoft's big pushes for SharePoint 2013 for On Premise is to consolidate multiple web applications to a single Web Application. This is easier said than done and depending on the complexity of one's environment this can take some time to unravel all of the moving parts like moving from path based to host name site collections. This blog focuses on how should I handle the sign in experience when a web application requires multiple authentication providers? In my test case scenario, I require SAML Authentication for my users.

Question: Wait, I thought you mentioned multiple authentication providers?
Answer: Yes, I did and keep reading below!

Because I require SAML Authentication enabled on the web application, I also require Windows Claims authentication enabled on the web application. This is required by Search in order to crawl the site.

For Example, Looking at my Authentication Provider my Web Application in Central Administration:

 

 

 

Traditional sign-in experience

After enabling dual authentication providers in a single web application, a default out of the box login page is presented to the users when they first sign in. It looks like the following:

 

This might be acceptable to smaller SharePoint environments especially if some of the users will leverage Windows Authentication and others SAML Authentication. However, most larger SharePoint Enterprises require that all users leverage SAML for Authentication purposes and leave Windows Claims Authentication enabled for Search. Because of this requirement, users shouldn't be presented with a choice (login page) and should automatically authenticate using SAML authentication.

 

 

Options for Resolution

You have a couple of options if you want to force users to always redirect to SAML authentication for initial logon while Search will leverage Windows Claims behind the scenes.

 

Option 1: Set /_trust/ as the custom sign in page for the default zone

It's super easy to implement this as you perform the following steps:

1. Launch Central Administrator and select Application Management

2. Select Manage Web Applications

3. Select desired Web Application and choose Authentication Providers button from the Ribbon

4. Select the appropriate Zone. In my case it's the Default Zone.

5. Scroll down to Sign In Page URL section and select "Custom Sign In Page" and input /_trust/

6. Click Save

 

 

Option 2: Use a custom login page

It's possible to build your own custom login page to automatically direct all user based traffic to the SAML Authentication Provider thereby bypassing the oob custom login page. Steve Peschka did an excellent job of outlining exactly how to accomplish this for SharePoint 2010 here. I figured why not reuse what Steve has already provided and validate it works fine for SharePoint 2013. I had to perform some tweaks for the following reasons:

Reason 1: Steve's article walks you through redirecting to a Forms Based Auth Provider. I will redirect to a SAML Authentication Provider

Reason 2: My code sample is different because were dealing with redirecting to a different Auth Providers and SharePoint Product

All of the steps below are identical with Steve's article however the code samples are different in my article as it applies to forcing redirection to SAML Authentication Provider.

See below for more details:

  1. Make a backup copy of the default.aspx file in the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\IDENTITYMODEL\LOGIN folder
  2. In Microsoft Visual Studio, create a Windows Class Library project.
  3. Add references to System.Web, Microsoft.SharePoint.dll, and Microsoft.SharePoint.IdentityModel.dll. The identity model assembly is in the global assembly cache. Therefore, I had to get a copy and place it in the root of my drive to add my references.
  4. Strong name the assembly that you are creating, because you will place it the global assembly cache later.
  5. Add a new ASPX page to your project. I find the easiest way to do this is to copy a page from an existing ASP.NET web application project; if you do this, you can copy the .aspx, .aspx.cs, and .aspx.designer.cs files all at the same time. Remember, in this case we want a file that is named "default.aspx", and it will be easier if there is no code written in it yet and there is minimal markup in the page.
  6. In the code-behind file (.aspx.cs file), change the namespace to match the namespace of your current project.
  1. Change the class so that it inherits from Microsoft.SharePoint.IdentityModel.Pages.MultiLogonPage.
  2. Override the OnLoad event:

My Code sample for Default.aspx.cs:

// Microsoft provides programming examples for illustration only,
// without warranty either expressed or implied, including, but not
// limited to, the implied warranties of merchantability and/or
// fitness for a particular purpose.
//
// This sample assumes that you are familiar with the programming
// language being demonstrated and the tools used to create and debug
// procedures. Microsoft support professionals can help explain the
// functionality of a particular procedure, but they will not modify
// these examples to provide added functionality or construct
// procedures to meet your specific needs. If you have limited
// programming experience, you may want to contact a Microsoft
// Certified Partner or the Microsoft fee-based consulting line at
//  (800) 936-5200
// For more information about Microsoft Certified Partners, please
// visit the following Microsoft Web site:
// https://partner.microsoft.com/global/30000104

using System;
     using System.Diagnostics;
     using Microsoft.SharePoint;
     using Microsoft.SharePoint.WebControls;
     using Microsoft.SharePoint.IdentityModel;
     namespace LoveAuth
     {
         public partial class authIsFun : Microsoft.SharePoint.IdentityModel.Pages.MultiLogonPage
         {
             protected override void OnLoad(EventArgs e)
             {
                 base.OnLoad(e);
                 try
                 {
                     // If this is not a post back, the user has not yet selected which
                     // authentication provider they want to use.
                     // In this case, we want to always refer the user to log in by using forms-based authentication.
                     if (!this.IsPostBack)
                     {
                         // Grab the query string
                         System.Text.StringBuilder qp = new System.Text.StringBuilder("trust=SAMLProvider&ReturnUrl=%2f_layouts%2f15%2fAuthenticate.aspx%3fSource%3d%252F&Source=%2F");
                         // Redirect to the saml-based authentication login page.
                         this.Response.Redirect("/_trust/default.aspx?" + qp.ToString());
                     }
                 }
                 catch (Exception ex)
                 {
                     Debug.WriteLine(ex.Message);
                 }
             }
             protected void Page_Load(object sender, EventArgs e)
             {
             }
         }
     }

 

  • Compile the application so that you can get the strong name for it and add it to the markup for default.aspx
  • Copy the following markup into default.aspx; you just have to change the class from which the page inherits (in this example, "MultiAuthLoginPage._Default,MultiAuthLoginPage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=907bf41ebba93579"). Note that all I did was copy it from /_login/default.aspx and replace the Inherits value with my custom class information.

My Markup for default.aspx looks like:

<%@ Assembly Name="LoveAuth, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7a7aa1fb5bbe489a" %>
< %@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LoveAuth.authIsFun" DynamicMasterPageFile="~masterurl/default.master" %>
< %@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
< %@ Assembly Name="Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Register Tagprefix="SharepointIdentity" Namespace="Microsoft.SharePoint.IdentityModel" Assembly="Microsoft.SharePoint.IdentityModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Assembly Name="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
< %@ Import Namespace="Microsoft.SharePoint.WebControls" %>
< %@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Import Namespace="Microsoft.SharePoint" %> <%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
< %@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
< %@ Import Namespace="Microsoft.SharePoint" %>
< %@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
    <SharePoint:EncodedLiteral runat="server"  EncodeMethod="HtmlEncode" Id="ClaimsLogonPageTitle" />
< /asp:Content>
< asp:Content ID="Content2" ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
    <SharePoint:EncodedLiteral runat="server"  EncodeMethod="HtmlEncode" Id="ClaimsLogonPageTitleInTitleArea" />
< /asp:Content>
< asp:Content ID="Content3" ContentPlaceHolderId="PlaceHolderSiteName" runat="server"/>
< asp:Content ID="Content4" ContentPlaceHolderId="PlaceHolderMain" runat="server">
< SharePoint:EncodedLiteral runat="server"  EncodeMethod="HtmlEncode" Id="ClaimsLogonPageMessage" />
< br />
< br />
< SharepointIdentity:LogonSelector ID="ClaimsLogonSelector" runat="server" />
< /asp:Content>

Finally for Each WFE: Register your assembly in the global assembly cache and copy your new custom default.aspx page into the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\IDENTITYMODEL\LOGIN folder.

Resources: https://msdn.microsoft.com/en-us/library/office/hh237665(v=office.14).aspx

 

Thanks!

Russ Maxwell, MSFT