SharePoint webpart with HTTP session timeouts and authentication popups


Requirement:

Though SharePoint sites are windows authenticated, I had a requirement to implement a webpart to pull data from confidential data source which mandated end users to authenticate every 15 minutes.

Design:

After some brainstorming the design is finalized to set authentication timeout on SP webpart such that there wont be any data leak even if the user leaves the desktop unlocked.

OOTB SharePoint webpages doesn’t support Session. But session objects can be used in code behind by creating “session state service application” and authentication popups can be called from Page_Load event of webpart using SP.UI.ModalDialog

Implementation:

Step 1: Create Session state service application

Run the below powershell script as a Farm Admin to create Session State Service application. (This Service app is not available from Central Admin GUI)

C:\PS>Enable-SPSessionStateService -DatabaseName "Session State Database" -DatabaseServer "localhost" -SessionTimeout 30

Session values can be changed by running

Get-SPSessionStateService

Set-SPSessionStateService –SessionTimeout 1

For testing purposes session values are reset to 1 min. This will create session database and new values are updated on creation of each session

Session_DB

Step 2: Add authentication page on the layout folder

Now create an application page for authentication under _layouts folder. This will have two controls for username and password. Now this page will be called from the webpart when the authentication is needed. Make sure EnableSessionState=”True” is added to page.


<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SignInPage.aspx.cs" Inherits="SignInPage.Components.SignInPage" DynamicMasterPageFile="~masterurl/default.master" EnableSessionState="True" %>
<%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ 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="wssuc" TagName="InputFormSection" Src="/_controltemplates/15/InputFormSection.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" Src="/_controltemplates/15/InputFormControl.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" Src="/_controltemplates/15/ButtonSection.ascx" %>

<asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
 <SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" text="Signin Page" EncodeMethod='HtmlEncode'/>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
 <SharePoint:EncodedLiteral ID="EncodedLiteral2" runat="server" text="Signin Page" EncodeMethod='HtmlEncode'/>
</asp:Content>
<asp:Content ID="PageDescription" ContentPlaceHolderID="PlaceHolderPageDescription" runat="server">
 <SharePoint:EncodedLiteral ID="EncodedLiteral3" runat="server" text="Page Description" EncodeMethod='HtmlEncode'/>
</asp:Content>
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">

<table id="maintable" border="0" cellspacing="0" cellpadding="0" class="ms-propertysheet" width="100%">
 <wssuc:InputFormSection runat="server">
 <Template_Description>
 <SharePoint:EncodedLiteral ID="EncodedLiteral4" runat="server" text="Please enter your Windows password to continue" EncodeMethod="HtmlEncode"/>
 </Template_Description>
 <Template_InputFormControls>
 <wssuc:InputFormControl runat="server">
 <Template_Control>
 <table border="0" cellspacing="1">
 <tr>
 <td class="ms-authoringcontrols" colspan="2" nowrap="nowrap">
 <SharePoint:EncodedLiteral ID="userName" runat="server" text="Windows User" EncodeMethod="HtmlEncode"/>
 <font size="4"> </font><br />
 </td>
 </tr>
 <tr>
 <td dir="ltr">
 <SharePoint:InputFormTextBox title="FormSection1Input" class="ms-input" ID="passwordBox" Columns="35" Runat="server" maxlength="255" size="60" width="100%" TextMode="Password" />
 </td>
 </tr>
 <tr>
 <td style="line-height:10px">&nbsp;</td>
 </tr>
 <tr>
 <td style="line-height:10px">&nbsp;</td>
 </tr>
 <tr>
 <td>
 <asp:Button runat="server" class="ms-ButtonHeightWidth" ID="BtnOk" Text="Ok" style="margin-left:0px" />
 </td>
 </tr>
 <tr>
 <td>
 <SharePoint:EncodedLiteral ID="errorMessage" runat="server" text="" EncodeMethod="HtmlEncode" />
 <font size="4"> </font><br />
 </td>
 </tr>
 </table>
 </Template_Control>
 </wssuc:InputFormControl>
 </Template_InputFormControls>
 </wssuc:InputFormSection>

 </table>

 <SharePoint:FormDigest ID="FormDigest1" runat="server" />
</asp:Content>

Popup

Since this authentication page is loaded as Modal popup, register CloseForm() script on the page to close the popup when the password matches.


namespace SignInPage.Components
{
 using System;
 using System.Security.Permissions;
 using System.Web;
 using Microsoft.SharePoint;
 using Microsoft.SharePoint.Security;
 using Microsoft.SharePoint.WebControls;
 using Microsoft.SharePoint.Administration.Claims;
 using Microsoft.Office.Server.Diagnostics;
 using Microsoft.IdentityModel.Claims;
 using System.DirectoryServices.AccountManagement;

/// <summary>
 /// TODO: Add comment for SignInPage
 /// </summary>
 [SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel = true)]
 public partial class SignInPage : LayoutsPageBase
 {
 private const string SESSION_STATUS_KEY = "{7CF253B3-6C3C-4B7A-A77B-D20736A74F25}";

/// <summary>
 /// Initializes a new instance of the SignInPage class
 /// </summary>

// Get current user claims
protected IClaimsIdentity claimsIdentity =
 HttpContext.Current.User.Identity as IClaimsIdentity;

public SignInPage()
 {
 }

/// <summary>
 /// Sets the inital values of controls
 /// </summary>
 /// <param name="e">Event arguments</param>
 protected override void OnLoad(EventArgs e)
 {
 passwordBox.Focus();

if (Session[SESSION_STATUS_KEY] == null)
 {
 SPSite siteCollection = this.Site;
 SPWeb site = this.Web;
 string[] windowsLogin = claimsIdentity.GetDisplayName().ToString().Split(':');
 userName.Text = windowsLogin[0];
 BtnOk.Click += new EventHandler(BtnOk_Click);
 errorMessage.Text = "";
 string script = @"
 <script type=text/javascript>
 function CloseForm() {
 window.frameElement.cancelPopUp();
 return false;}
 </script>";
 ClientScript.RegisterClientScriptBlock(this.GetType(), ClientID, script);
 }
 else
 {
 PortalLog.LogString("session true " + DateTime.Now.ToString());
 string script = @"
 <script type=text/javascript>
 function CloseForm() {
 window.frameElement.cancelPopUp();
 return false;}
 </script>";
 ClientScript.RegisterClientScriptBlock(this.GetType(), ClientID, script);
 ClientScript.RegisterStartupScript(this.GetType(), ClientID, "CloseForm();", true);
 PortalLog.LogString(" form closed " + DateTime.Now.ToString());
 }
 }

/// <summary>
 /// TODO: Add comment
 /// </summary>
 /// <param name="sender">Sender of the event</param>
 /// <param name="e">Arguments of the event</param>
 private void BtnOk_Click(object sender, EventArgs e)
 {

if (IsPostBack)
 {
 try
 {
 string passwordText = passwordBox.Text;
 if (passwordText != "")
 {
 string[] username = claimsIdentity.Claims[0].ToString().Split('\\');
 bool valid = false;
 using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
 {
 valid = context.ValidateCredentials(username[1], passwordText);
 }

if (valid)
 {
 errorMessage.Text = "Password is right";
 PortalLog.LogString("Signin Current Time Is " + DateTime.Now.ToString());
 Session[SESSION_STATUS_KEY] = DateTime.Now.ToString(); // Use current time
 PortalLog.LogString("Signin Updated Session Status :" + Session[SESSION_STATUS_KEY]);
 ClientScript.RegisterStartupScript(this.GetType(), ClientID, "CloseForm();", true);
 PortalLog.LogString("Signin Reg Close Script OK");

 }
 else
 {
 errorMessage.Text = "Incorrect Password please try again";
 }
 }
 else
 {
 errorMessage.Text = "Please enter your password";
 }

}
 catch (Exception ex)
 {
 PortalLog.LogString("Signin Exception:" + ex.ToString());
 }

}
 }

}
}

Here ClientScript.RegisterClientScriptBlock registers the javascript on page load and ClientScript.RegisterStartupScript calls CloseForm() function from the registered script. context.ValidateCredentials(username[1], passwordText) validates the password.

Step 3: Call authentication page fron “Page_Load” event

Now call the authentication script on Page_Load event of the webpart.


protected void Page_Load(object sender, EventArgs e)
{
System.Web.SessionState.HttpSessionState session = System.Web.HttpContext.Current.Session;
AllowDocumentRetreival = session != null ? session[SESSION_STATUS_KEY] != null : false;

if(AllowDocumentRetreival)
	{
string script =
                   @"
                    function dialogCallback(dialogResult, returnValue){window.location.reload();}
                    function ShowModal() {
                   ExecuteOrDelayUntilScriptLoaded(function () {
                    var options = {
                        url: '/_layouts/15/signinpage/signinpage.aspx',
                        title: 'Signin',
                        width: 500,
                        height: 210,
                        allowMaximize: true,
                        showClose: false,
                        dialogReturnValueCallback: dialogCallback
                    };
                 SP.UI.ModalDialog.showModalDialog(options);
                }, 'sp.js');}";

            ScriptManager.RegisterClientScriptBlock(this, this.GetType(), ClientID, script, true);
            ScriptManager.RegisterStartupScript(this, this.GetType(), ClientID, "ShowModal();", true);
	}

}

SP.UI.ModalDialog.showModalDialog(options) calls the signin page created in the last step. On successful authentication closeform() function calls  window.frameElement.cancelPopUp() to close the popup.

Session state troubleshooting tips:

In few scenarios Session object always return null though session value exists in the database. This could be cache. To resolve this issue navigate to C:\ProgramData\Microsoft\SharePoint\Config and delete all the files in the GUID named folders except the .INI file and restart the SharePoint timer service.

Advertisements