Sessions and user state
Orchestrator instances keep track of sessions and user state. It is important to remember that a Maverics session
contains much more than just the user’s authentication state. Because of this, it should not be confused with authentication sessions represented in a session cookie or session database.
For example, an Orchestrator may redirect a user to an identity provider such as Azure AD for authentication. Upon success, it retrieves additional attributes from a directory service, database, or an API-exposed service. The authenticated session, user attributes, and any other information associated with the request make up the state of the user and is held in the Maverics session
. This state is used by components within the Orchestrator to extend a cloud identity service such as Azure AD to protect on-premises applications or migrate users and their credentials from on-premises identity systems to a cloud identity system.
Configuration options
session
declares the set of configuration options for session management.
Cookie
cookie
is an optional field used to define settings for the HTTP cookie that is
used to tie a user back to their session.
Domain
domain
is an optional field that specifies the hosts to which the session cookie
will be sent. This should normally be set only when proxying to multiple apps on the
same domain. If not set, the cookie domain attribute
will be set to the hostname requested by the client.
For example, if domain
is set to example.com
, the user agent will include the
cookie in the Cookie
header when making HTTP requests to example.com
and all
subdomains of *.example.com
.
Name
name
is an optional field that specifies the name of the session cookie used by the
Maverics Orchestrator. The default session cookie name is maverics_session
.
Disable HTTP Only
disableHTTPOnly
is an optional field that disables the
HttpOnly
cookie attribute. Defaults to
false. If set to true, the session cookie will not have the HttpOnly
attribute,
allowing the cookie to be accessed via client side scripts.
Disable Secure
disableSecure
is an optional field that disables the
Secure
cookie attribute.
Defaults to false. If set to true, the session cookie will not have the Secure
attribute, allowing the browser to send the cookie over an unencrypted HTTP request.
Max Lifetime
maxLifetimeSeconds
is an optional field that represents the maximum number of seconds
that can elapse post-authentication before a session’s authentication state becomes
invalidated. If a negative value is specified, the session will remain authenticated
indefinitely. If no value is specified, the default is 24 hours.
Idle Timeout
idleTimeout
is an optional field that represents the number of seconds a session may
remain idle before timing out. If no value is set, or IdleTimeout is set to 0, then
the session idle timeout is disabled.
Eval Session Max Lifetime
evalMaxLifetimeSE
is an optional field to define a Service Extension that
determines how sessions reaching their max lifetime are handled. maxLifetimeSeconds
is still used for individually expiring attributes.
Eval Session Idle Timeout
evalIdleTimeoutSE
is an optional field to define a Service Extension that
determines how session idle timeouts are handled. If this Service Extension is
defined, then the idleTimeout
value is ignored.
Cache Size
cacheSize
is an optional field that limits the number of sessions maintained in
memory. When the session cache has reached its maximum size, and a new session is
requested, the session management removes the least recently used session from the
cache to make room for the new one. If cacheSize
is not set, a default value of
50000 users sessions will be used.
Examples
Base Configuration
This configuration will trigger a session expiration after the user is idle for 30 minutes and after a maximum period of one hour. On session expiration, the user will be required to reauthenticate with the IDPs associated with the defined policy.
{% hint style=“info” %} It is worth noting that a federated IDP may still hold an active session for the user during re-authentication which may result in the user not being prompted for their credentials. To completely log a user out of all IDPs, follow the single logout example below. {% endhint %}
session:
cookie:
domain: example.com
disableHTTPOnly: false
disableSecure: false
maxLifetimeSeconds: 3600
idleTimeout: 1800
Session Expiration via Service Extension
Service extensions allow complete flexibility over the process of controlling a user’s session lifespan. The example below triggers single logout on a session expiry event. The max session timeout and idle session timeout are determined dynamically depending on the user’s employee type (full-time employee vs contractor).
/etc/maverics/maverics.yaml
session:
cookie:
domain: example.com
evalMaxLifetimeSE:
funcName: EvalMaxLifetime
file: /etc/maverics/extensions/session.go
evalIdleTimeoutSE:
funcName: EvalIdleTimeout
file: /etc/maverics/extensions/session.go
singleLogout:
logoutURL: https://example.com/single-logout
postLogout:
redirectURL: https://example.com/index.html
/etc/maverics/extensions/session.go
package main
import (
"net/http"
"time"
"github.com/strata-io/service-extension/orchestrator"
)
const (
sloEndpoint = "https://example.com/single-logout"
contractorIdleTimeoutSeconds = 300
contractorMaxTimeoutSeconds = 3600
fteIdleTimeoutSeconds = 3600
fteMaxTimeoutSeconds = 43200
)
// EvalMaxLifetime determines whether a session has reached its max lifetime. The
// expiration check is informed by a whether a user is a full-time employee or
// contractor.
func EvalMaxLifetime(
api orchestrator.Orchestrator,
rw http.ResponseWriter,
req *http.Request,
createdAt time.Time,
) bool {
logger := api.Logger()
sess, err := api.Session()
if err != nil {
logger.Error(
"se", "failed to retrieve session",
"err", err,
)
return false
}
employeeType, err := sess.GetString("okta.employeeType")
if err != nil {
logger.Error(
"se", "failed to retrieve 'employeeType'",
"err", err,
)
return false
}
maxTimeout := contractorMaxTimeoutSeconds
if employeeType == "full_time" {
maxTimeout = fteMaxTimeoutSeconds
}
if sessionExpired(createdAt, maxTimeout) {
logger.Info(
"se", "user has reached max session lifetime: redirecting to SLO endpoint")
http.Redirect(rw, req, sloEndpoint, http.StatusFound)
// False is returned here so that the SLO endpoint is aware which IDPs to log
// the user out of. If true is returned, all session attributes including the
// list of authenticated IDPs will be cleared.
return false
}
return false
}
// EvalIdleTimeout determines whether a session has reached its idle timeout. The
// expiration check is informed by a whether a user is a full-time employee or
// contractor.
func EvalIdleTimeout(
api orchestrator.Orchestrator,
rw http.ResponseWriter,
req *http.Request,
lastAccess time.Time,
) bool {
logger := api.Logger()
sess, err := api.Session()
if err != nil {
logger.Error(
"se", "failed to retrieve session",
"err", err,
)
return false
}
employeeType, err := sess.GetString("okta.employeeType")
if err != nil {
logger.Error(
"se", "failed to retrieve 'employeeType'",
"err", err,
)
return false
}
idleTimeout := contractorIdleTimeoutSeconds
if employeeType == "full_time" {
idleTimeout = fteIdleTimeoutSeconds
}
if sessionExpired(lastAccess, idleTimeout) {
logger.Info("se", "user has reached idle session lifetime: redirecting to SLO endpoint")
http.Redirect(rw, req, sloEndpoint, http.StatusFound)
// False is returned here so that the SLO endpoint is aware which IDPs to log
// the user out of. If true is returned, all session attributes including the
// list of authenticated IDPs will be cleared.
return false
}
return false
}
func sessionExpired(startPoint time.Time, timeout int) bool {
return startPoint.Before(time.Now().Add(-time.Duration(timeout) * time.Second))
}