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.

ℹ️
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.
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))
}