Sessions

Prev Next

Overview

You have the option of customizing session handling using two functions: evalMaxLifetimeSE and evalIdleTimeoutSE.

  • Evaluate Max Session Lifetime (evalMaxLifetimeSE) determines how sessions reaching their max lifetime are handled. While maxLifetimeSeconds is still used for individually expiring attributes, evalMaxLifetimeSE checks if the user has been logged in for too long (beyond MaxTimeout), and logs them out if so.

  • Evaluate Idle Session Timeout (evalIdleTimeoutSE) checks if the user has been inactive for too long and logs them out if so. If this Service Extension is defined, the idleTimeout value is ignored.

Implementation

Service extensions allow complete flexibility over the process of controlling a user's session lifespan.

To create these service extensions, go to Service Extensions.

  1. From the right sidebar, click the + icon next to Evaluate Max Session Lifetime or Evaluate Idle Session Timeout.

  2. Enter a name and description for the service extension, and click Create.

  3. The service extension code appears on the next page. From here, you can add assets, providers, and/or metadata. When you've finished adding this information, click Update.

  4. You will receive a confirmation message in the lower right corner that the service extension file has been updated.

You can now use this service extension in an existing or new deployment.

  1. Go to the Deployments screen and select an existing deployment from the list or click Create.  

  2. Under Orchestrator Settings, next to Session and Cookie, click Edit.

  1. The service extensions you created can be selected from the dropdown menus under Evaluate Session Maximum Lifetime Service Extension and/or Evaluate Session Idle Timeout Service Extension.

Code example

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/extensions/session.go

package main

import (
	"net/http"
	"time"

	"github.com/strata-io/service-extension/orchestrator"
)

// These constants define the Single Logout (SLO) endpoint and timeout values
// based on the type of user (contractor or full-time employee).
const (
	sloEndpoint = "https://example.com/single-logout"

	// Contractor session timeout settings
	contractorIdleTimeoutSeconds = 300    // 5 minutes of inactivity
	contractorMaxTimeoutSeconds  = 3600   // 1 hour total session duration

	// Full-time employee session timeout settings
	fteIdleTimeoutSeconds = 3600          // 1 hour of inactivity
	fteMaxTimeoutSeconds  = 43200         // 12 hours total session duration
)

// EvalMaxLifetime checks if a user's session has exceeded the maximum allowed duration,
// depending on whether the user is a full-time employee or a contractor.
func EvalMaxLifetime(
	api orchestrator.Orchestrator,
	rw http.ResponseWriter,
	req *http.Request,
	createdAt time.Time, // The time the session was created
) bool {
	logger := api.Logger()

	// Get the current session from the orchestrator
	sess, err := api.Session()
	if err != nil {
		// Log an error and return false if we can't get the session
		logger.Error("se", "failed to retrieve session", "err", err)
		return false
	}

	// Retrieve the user's employee type from the session (e.g., "full_time" or "contractor")
	employeeType, err := sess.GetString("okta.employeeType")
	if err != nil {
		// Log an error if we fail to retrieve the employee type
		logger.Error("se", "failed to retrieve 'employeeType'", "err", err)
		return false
	}

	// Set the default timeout to the contractor's max timeout
	maxTimeout := contractorMaxTimeoutSeconds

	// If the user is a full-time employee, override the timeout with the FTE value
	if employeeType == "full_time" {
		maxTimeout = fteMaxTimeoutSeconds
	}

	// Check if the session has passed the maximum lifetime
	if sessionExpired(createdAt, maxTimeout) {
		// Log the event and redirect the user to the logout (SLO) endpoint
		logger.Info("se", "user has reached max session lifetime: redirecting to SLO endpoint")
		http.Redirect(rw, req, sloEndpoint, http.StatusFound)

		// Return false to allow SLO endpoint to perform proper logout steps.
		// If we returned true, it might clear session data prematurely.
		return false
	}

	// Session is still valid
	return false
}

// EvalIdleTimeout checks if a user's session has been idle (inactive) for too long,
// using different timeouts for full-time employees and contractors.
func EvalIdleTimeout(
	api orchestrator.Orchestrator,
	rw http.ResponseWriter,
	req *http.Request,
	lastAccess time.Time, // The last time the user was active
) bool {
	logger := api.Logger()

	// Get the session from the orchestrator
	sess, err := api.Session()
	if err != nil {
		// Log and return if we can't get the session
		logger.Error("se", "failed to retrieve session", "err", err)
		return false
	}

	// Retrieve the employee type from the session
	employeeType, err := sess.GetString("okta.employeeType")
	if err != nil {
		logger.Error("se", "failed to retrieve 'employeeType'", "err", err)
		return false
	}

	// Set the default idle timeout to contractor's idle time
	idleTimeout := contractorIdleTimeoutSeconds

	// Use a longer timeout for full-time employees
	if employeeType == "full_time" {
		idleTimeout = fteIdleTimeoutSeconds
	}

	// Check if the session has been idle for too long
	if sessionExpired(lastAccess, idleTimeout) {
		logger.Info("se", "user has reached idle session lifetime: redirecting to SLO endpoint")
		http.Redirect(rw, req, sloEndpoint, http.StatusFound)

		// Return false to signal that logout should be handled by SLO endpoint
		return false
	}

	// Session is still active within the allowed idle time
	return false
}

// sessionExpired is a helper function that checks whether a given time (like session start
// or last activity) is older than the allowed timeout period.
func sessionExpired(startPoint time.Time, timeout int) bool {
	// Convert the timeout to a time duration and subtract it from the current time.
	// If the start time is before that, the session has expired.
	return startPoint.Before(time.Now().Add(-time.Duration(timeout) * time.Second))
}