SharePoint 2016: FBA authentication changes
Disclaimer: The below is a summary of observations made as the result of some reverse-engineering and Source Code review. It’s not necessarily to be taken as “official,” but does check out according to my testing.
This is post is not about configuring Forms-based Authentication (FBA). There’s plenty of other posts out there about that. The way you enable FBA has not really changed in SharePoint 2016. You still make the same changes to the Web Application and Security Token Service (STS) web.config files.
During a recent troubleshooting session, I noticed a change in behavior in FBA in SharePoint 2016 as compared to 2010 and 2013. This post is a summary of what I found.
Cookie / Session Refresh
In SharePoint 2016, we added several new methods to the Source Code that change the way an FBA session is handled.
In SharePoint 2010 / 2013, if the user presented a valid FedAuth cookie to the server, but the users logon token (cached server-side) was expired, the FedAuth cookie was rejected, and the user was redirected back to the FBA login page.
In SharePoint 2016, we added functionality to automatically refresh the users FedAuth cookie without forcing them to log in again. I believe this was added to relieve some customer pain points in SharePoint Online (SPO), which we then inherited in SharePoint on-premise.
This may create some unexpected behavior. For example, consider the following scenario:
You want your user to be forced to log in again after 1 hour regardless if they’ve been active in the site or not, so you set FormsTokenLifetime to 1 hour:
$sts = Get-SPSecurityTokenServiceConfig
$sts.FormsTokenLifetime = (New-TimeSpan -Hours 1)
$sts.update()
You log in and leave the browser open for more than 1 hour. You then refresh the page or click on some other link within the site. You expect to be redirected back to the FBA login page, but instead, you find you’re still logged in and the page renders without prompt.
This happens due to new automatic cookie refresh functionality. If your cookie is still valid, we will automatically refresh your login token and send you a new FedAuth cookie without prompting you to login again.
In order to force the user back to the login page, you would need to change the CookieLifetimeRefreshWindow value within SPSecurityTokenServiceConfig.
However the “correct” value for CookieLifetimeRefreshWindow depends on a couple other values within the security token service config. It must be greater than FormsTokenLifetime and WindowsTokenLifetime, but less than CookieLifetime.
If you try to set it otherwise, it will throw this error:
PS C:\Users\josh> $sts.CookieLifetimeRefreshWindow = (New-TimeSpan -hours 1)
Exception setting "CookieLifetimeRefreshWindow": "Specified argument was out of the range of valid values
Parameter name: value"
Also, LogonTokenCacheExpirationWindow must be less than FormsTokenLifetime and WindowsTokenLifetime. The default value is 10 minutes, so that’s usually pretty safe, but you may need to adjust that while you’re at it.
In order to meet my above hypothetical scenario (user forced to login again after 1 hour of inactivity), you’d want to set all the values to something like this:
$sts = Get-SPSecurityTokenServiceConfig
$sts.FormsTokenLifetime = (New-TimeSpan -Hours 1)
$sts.WindowsTokenLifetime = (New-TimeSpan -Hours 1)
$sts.LogonTokenCacheExpirationWindow = (New-TimeSpan -Minutes 1)
$sts.CookieLifetime = (New-TimeSpan -Hours 1 -Minutes 1)
$sts.CookieLifetimeRefreshWindow = (New-TimeSpan -Hours 1)
$sts.update()
iisreset
# Then run IISReset on other boxes in the farm to clear logon tokes from in-memory cache
# You also need to restart the AppFabric Caching service on your Distributed Cache servers to clear the existing logon tokens
# Failure to restart IIS and D-cache may result in inconsistent token refresh behavior
Note: If you’re still not getting the logout behavior you expect based on the values you set, you may also have to look at the controls on your sites / pages.
There may be controls that keep issuing requests even when you’re not active in the browser.
For example, in an out-of-box Team site, there’s a NewsFeed web part on the home page. There is JavaScript on the page that issues a client-side object model (CSOM) call every so often to automatically refresh the feed. Those CSOM calls are authenticated and will result in a logon token refresh and a FedAuth cookie reissue.
This can be problematic in situations where you want the user to be forced to log in again after a certain amount of time regardless of how active the user has been.
As long as authenticated requests are made within your cookie lifetime, your session will keep getting refreshed. That’s called a “sliding session”. It’s by-design for FBA, and I’m not sure there’s any way to disable it.
You can run Fiddler to see if this is happening. Just log in and let the browser sit there for a few minutes. See if any new requests pop up in Fiddler, for example, requests to _vti_bin/client.svc/ProcessQuery.
Cookie Persistence
This is not necessarily “new” for SharePoint 2016, but it came up as part of my investigation, so I thought I’d share some things that I haven’t seen documented elsewhere.
First, lets talk about some basics. There’s two options for cookie persistence:
Session = The cookie (FedAuth) is stored in your browser memory. If you close the browser, it’s gone. When you open a new browser, you will be forced to log in again.
Persistent = The FedAuth cookie is written to disk on your client machine. You can close your browser (or even reboot) all you want, when you hit the site again, the cookie will be presented to the server, and you will be authenticated again without logging in. This cookie can also be shared across other applications. For example, Excel can use this same cookie when authenticating to SharePoint. That’s not possible with a Session cookie. In that case, every application needs to authenticate separately.
Your cookie type is controlled using the UseSessionCookies property of the SPSecurityTokenServiceConfig. The default value is “false”.
UseSessionCookies = True = Session Cookie
UseSessionCookies = False = Persistent Cookie
There’s also the “Sign me in automatically” check box on the out-of-box login form. If you use a custom login form, you may or may not have this functionality.
If “Sign me in automatically” is not selected, you will get a Session cookie regardless of what the UseSessionCookies property is set to.
Here I have it set to False, which should mean a Persistent cookie:
But if I don’t select the checkbox…
… I will get a Session cookie, which you can see in a Fiddler trace if you base-64 decode the FedAuth cookie.
Find the FedAuth cookie in the client request (top-right pane), right-click on it and choose “Send to TextWizard”:
Choose “From Base64” in the “Transform” drop-down, and delete the “FedAuth=” part in the upper pane.
Now you should be able to see the decoded FedAuth cookie, and the persistence setting.
Session Cookie:
False = Session Cookie
No Expiration Set.
In the Fiddler trace, find where the server set the FedAuth cookie. You’ll notice that a Session cookie does not specify a client-side expiration:
FedAuth=77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48U1A+<truncated for brevity>+; path=/; HttpOnly
Persistent Cookie:
True = Persistent cookie
Expiration is set.
In the Fiddler trace, find where the server set the FedAuth cookie. You’ll notice that a Persistent cookie does specify a client-side expiration:
FedAuth=77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48U1A+<truncated for brevity>=; expires=Wed, 11-Jul-2018 16:35:20 GMT; path=/; HttpOnly