Monday, September 4, 2017

Sliding session for Sharepoint 2013 with FBA and persistent cookies

Sliding session allows user to use site without being reauthenticated if last action was done less than configured session lifetime. In Sharepoint 2013 FBA the following parameters of security token service config are used for setting session lifetime:

  • CookieLifetime
  • FormsTokenLifeTime
  • LogonTokenCacheExpirationWindow

They are well described in the following article SharePoint 2013 authentication lifetime settings and I won’t repeat it here. The problem is that when you use persistent cookies (i.e. those which are stored on client’s side) only CookieLifetime are actually used (to be more precise, FormsTokenLifeTime is used for setting initial ValidTo value for session security token). In addition to that sliding sessions doesn’t work by default, i.e. regardless of whether user made actions on the site or not he will be logged out after cookies will be expired. Persistent cookies can be set e.g. if user checked “Remember Me” checkbox on the login page:

   1: private bool AuthenticateFormsUser(Uri context, string username, string pwd,
   2:     bool rememberMe)
   3: {
   4:     if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(pwd))
   5:     {
   6:         return false;
   7:     }
   8:  
   9:     try
  10:     {
  11:         var formsAuthOption = SPFormsAuthenticationOption.None;
  12:         var tokenType = SPSessionTokenWriteType.WriteSessionCookie;
  13:         if (rememberMe)
  14:         {
  15:             formsAuthOption = SPFormsAuthenticationOption.PersistentSignInRequest;
  16:             tokenType = SPSessionTokenWriteType.WritePersistentCookie;
  17:         }
  18:  
  19:         var authProvider = GetAuthProvider(SPContext.Current.Site);
  20:         var securityToken = SPSecurityContext.SecurityTokenForFormsAuthentication(
  21:             context,
  22:             authProvider.MembershipProvider,
  23:             authProvider.RoleProvider,
  24:             username,
  25:             pwd,
  26:             formsAuthOption);
  27:  
  28:         var fam = SPFederationAuthenticationModule.Current;
  29:         fam.SetPrincipalAndWriteSessionToken(securityToken, tokenType);
  30:         return true;
  31:     }
  32:     catch (Exception)
  33:     {
  34:         return false;
  35:     }
  36: }

Here on lines 12-16 code checks whether rememberMe parameter is true and if yes uses persistent cookies.

So is it possible to have sliding expiration sessions when persistent cookies are used? The answer is yes, but in order to do that we will need custom HTTP module which will renew token on each request:

   1: public class SlidingSessionModule : IHttpModule
   2: {
   3:     public void Init(HttpApplication context)
   4:     {
   5:         FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived +=
   6:             SessionAuthenticationModule_SessionSecurityTokenReceived;
   7:     }
   8:  
   9:     private void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender,
  10:         SessionSecurityTokenReceivedEventArgs e)
  11:     {
  12:         try
  13:         {
  14:             if (e == null)
  15:             {
  16:                 return;
  17:             }
  18:             var sessionToken = e.SessionToken;
  19:             if (sessionToken == null)
  20:             {
  21:                 return;
  22:             }
  23:             if (claimsPrincipal == null)
  24:             {
  25:                 return;
  26:             }
  27:  
  28:             TimeSpan cookieLifetime = TimeSpan.FromSeconds(0);
  29:             SPSecurity.RunWithElevatedPrivileges(
  30:                 () =>
  31:                     {
  32:                         cookieLifetime = Microsoft.SharePoint.Administration.Claims.
  33:                             SPSecurityTokenServiceManager.Local.CookieLifetime;
  34:                     });
  35:  
  36:             DateTime utcNow = DateTime.UtcNow;
  37:             DateTime validFrom = utcNow;
  38:             DateTime validTo = utcNow + cookieLifetime;
  39:             var sam = FederatedAuthentication.SessionAuthenticationModule;
  40:             e.SessionToken = sam.CreateSessionSecurityToken(claimsPrincipal,
  41:                 sessionToken.Context, validFrom, validTo, sessionToken.IsPersistent);
  42:             e.ReissueCookie = true;
  43:         }
  44:         catch (Exception x)
  45:         {
  46:             // log
  47:         }
  48:     }
  49:  
  50:     public void Dispose()
  51:     {
  52:     }
  53: }

In the module we subscribe on SessionAuthenticationModule.SessionSecurityTokenReceived event (lines 5-6) and in event handler we renew token with extended ValidFrom and ValidTo properties (lines 36-42) which are set from CookieLifetime property of security token service config (lines 29-34) so you may continue configure it from PowerShell.

Then we need to install this module by adding dll to the GAC and the following line to the web.config <modules> section:

   1: <modules>
   2:   ..
   3:   <add name="SlidingSessionModule"
   4:     type="SlidingSessionModule.SlidingSessionModule, SlidingSessionModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." />
   5: </modules>

After that you will have sliding sessions with persistent cookies for Sharepoint FBA.

1 comment:

  1. Hi am getting "System.ArgumentException: Exception of type 'System.ArgumentException' was thrown. Parameter name: writeOperationType" once I call fam.SetPrincipalAndWriteSessionToken(securityToken, tokenType); with WritePersistentCookie

    do you have an idea what could cause this issue

    ReplyDelete