Safewhere Identify, as an intermediate Identity Provider, is a stateful web application that needs to manage states of logins. To learn about past common issues with session states, you can read our white paper.
The complexity of state management
At first, a typical login flow that Identify needs to handle is:
- I want to access a service provider.
- The service provider sends a request to Identify.
- Identify forwards a request to an upstream IdP. Identify needs to store the request's id in session for validation purpose later.
- Identify receives a response from the upstream IdP. Identify needs to validate if the response is for the request id that it saves in session.
- Identify returns a response to the service provider.
- I log out of the service provider.
- The service provider sends a logout request to Identify.
- Identify sends a logout request to the upstream IdP.
- The upstream IdP sends a logout response to Identify.
- Identify sends a logout response to the service provider.
In order to support steps (4)-(10), Identify needs to maintain "states" of the login session somewhere. There are two typical storages for maintaining states in Asp.Net: Asp.Net session state and cookies. Identify is using both for storing states and supports a few options that you can use to tweak where different types of data are stored as to best fit your deployment.
Session state versus cookies
The following table compares the key aspects that has impacted the design of Identify and should be considered for your deployment design:
|Data size||Enough to store any data that Identify may ever need.||Have cookie size and header size limits. IIS' header size limit is about 16KB by default.||Storing tokens (in encrypted form) in cookies is not feasible. This means that using pure cookies is not an option.|
|Persistence||Session state has expiry time. The default expiry duration is 20 minutes.||Cookie has two main expiry modes:
1. After a specific point of time.
2. Or when a browser is closed
|Data stored in cookies can still be available after a session has expired|
|Server dependent||InProc session state is stored in a specific server. In a redundant setup, you can use sticky session. SQL session state is server independent.||Independent of servers.||Using cookies makes data available even when a session has expired, or sticky session is not configured correctly.|
|Security||Users cannot directly edit data stored in session state.||Cookies are stored in users' browsers. To prevent tampering, we must encrypt data before writing to cookies.||Encryption increases data size significantly.|
Data types and where they can be stored
In terms of where a class of data can be stored, we have the following options:
|Data type||Where to store|
|The original login request from a service provider||Session state, Cookies|
|Core login context - mostly flags that can impact how a login is processed. Examples are if a request needs to re-login or a second factor needs to be done.||Session state, Cookies (encrypted)|
|Plugin-specific context||Session state, Cookies (encrypted): only support SAML plugin. Other plugins will be supported in future versions.|
|A token (ClaimsPrincipal) returned from an upstream Identity Provider||Session state|
|Short-lived data that is used for activities that happen mostly in Identify side such as claims transformations, consent, second factor.||Session state|
State management options
By default, Identify uses session state to store login data. We offer some settings that you can use to control whether some data types should be stored in cookies when possible:
- Save login context in cookies: when set to true, Identify persists login context data to cookies.
- Save request state in cookies: when set to true, Identify persists request state to cookies. Note that if the Save login context in cookies option is true, request state is always saved to cookies regardless of this setting.
- Context cookies lifetime (in minutes): how long those context cookies are kept. A zero value means those cookies are kept as long as the browser remains opened.
Use pure session state
In this mode, Identify stores all context data in session. The configuration is:
- Save login context in cookies: false
- Save request state in cookies: false
- Context in cookies lifetime (minutes): irrelevant because the other two settings are false.
This is the default configuration. Because session state does not have low data size limit as cookies, there is no problem with large requests or request headers. However, users might get errors about expired session state. In the 10-step login example, if a user stays at the Identity Provider long enough that causes the Identify's session to expire before finishing the login, the user will get an error when the Identity Provider returns a response to Identify. When that happens, Identify gives a clear error about the fact that the user's login session has expired.
When using this mode in a redundant setup, you need to either use:
- Sticky session
Or use SQL session state. SQL session state is recommended when:
a. Sticky session is not an option, for example, when each web server is placed in a different data center.
b. You don't need very high throughput and low latency. Using SQL Server session state increases latency and decreases throughputs in comparison to in-proc session state. However, if you have a very powerful SQL Server, the impact may be negligible.
Store as much data in cookies as possible
In this mode, Identify stores as much data in cookies as possible. The configuration is:
- Save login context in cookies: true
- Save request state in cookies: irrelevant because the Save login context in cookies setting is true.
- Context in cookies lifetime (minutes): 0 or for how long you want to keep those cookies available.
As mentioned in the Data types and where they can be stored section, the 3 notable types are:
- Core login context
- Plugin-specific context. Right now, only SAML plugin is fully supported. (1)
- Request state (2)
(1) means that when you are using the SAML 2.0 service provider -> Identify -> SAML 2.0 Identity provider setup (which a majority of Identify's customers are using), Identify can process a login successfully even if by the time that the upstream Identity provider returns a response, Identify's session state has expired as long as cookies are still available. This means that you need to set the cookies lifetime longer than session state lifetime (which is 20 minutes by default). We will add support for the OAuth2/OIDC plugin in a future version.
(2) For other plugins, even when the session expiry case happens, because the request state that stores the original login request is still available, Identify will be able to display an error message about what has gone wrong and offer the user an option to restart the login. The problem with this is that if the Service Provider is a SAML one that uses POST binding to send AuthnRequest, the request size is big so users are more likely to encounter the "HTTP 400 Bad Request (Request Header too long)" error. A POST-binding AuthnRequest with X509Data KeyInfo is usually 3-4KB. Opening 2 or 3 separate tabs and triggering AuthnRequest to be sent to Identify is enough for the error to happen.
- This restarting feature works for all 3 types of protocols: SAML, WSFederation, and OAuth/OIDC.
Store only request state in cookies
In this mode, Identify only stores request state in cookies. The configuration is:
- Save login context in cookies: false
- Save request state in cookies: true.
- Context cookies lifetime (in minutes): 0 or for how long you want to keep those cookies available.
In comparison to the previous configuration, login context is not stored in cookies. The consequences are:
- Identify cannot finish a login when an identity provider returns a response to Identify after the session has expired.
- Users can still restart their logins.
Because less data is stored in cookies, the chance of the "HTTP 400 Bad Request (Request Header too long)" error happening is reduced significantly.
a. The chance of the "Request Header too long" error happening is still high when POST binding is used to send AuthnRequest.
Increase the header size limit
You can reduce the chance of the "HTTP 400 Bad Request (Request Header too long)" error happening by increasing IIS' header size limit (you can read: https://docs.microsoft.com/en-US/troubleshoot/iis/http-bad-request-response-kerberos ). We recommend that you do not increase it to more than 32KB
Our recommendation is that you should use the default settings which use session state because that will work best out of the box. Other options require a lot of trial and error to find out what the optimal value for each setting is.
Even though the ability to continue a login when session state has expired is great, we expect that it would have limited applications due to the problems with long headers.
You will need to try out different values for the Context in cookies lifetime to see what works best for you. The longer the value is, the higher chance that your users can restart logins, but they are also more likely to encounter the header error. Let's say you set it to 60 minutes:
- For some reason, a user opens more tabs to log in without finishing any of them. In reality, this usually happens when the user is using a service provider in multiple tabs that automatically refresh user logins.
- The user closes all those tabs without closing the browser. After 50 minutes, the user accesses the service provider again.
- The service provider sends another request to Identify which causes the header error.
It could be that your deployment is not that complex, and your users never encounter the header error. As a side note, ADFS' default value for the equivalent setting is 10 minutes.